deploiement sur VM
This commit is contained in:
@@ -1,17 +0,0 @@
|
||||
<VirtualHost *:{{ http_port }}>
|
||||
ServerAdmin webmaster@localhost
|
||||
ServerName {{ http_host }}
|
||||
ServerAlias www.{{ http_host }}
|
||||
DocumentRoot /var/www/{{ http_host }}
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
<Directory /var/www/{{ http_host }}>
|
||||
Options -Indexes
|
||||
</Directory>
|
||||
|
||||
<IfModule mod_dir.c>
|
||||
DirectoryIndex index.php index.html index.cgi index.pl index.xhtml index.htm
|
||||
</IfModule>
|
||||
|
||||
</VirtualHost>
|
||||
@@ -3,12 +3,13 @@ services:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: dockerfiles/front/Dockerfile
|
||||
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ../../../doodlestudent/front:/app
|
||||
- ./certbot/www:/var/www/certbot:ro
|
||||
- ./certbot/conf:/etc/letsencrypt:ro
|
||||
- ../doodlestudent/front:/app
|
||||
- ../certbot/www:/var/www/certbot:ro
|
||||
- ../certbot/conf:/etc/letsencrypt:ro
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
depends_on:
|
||||
@@ -20,14 +21,14 @@ services:
|
||||
- app-network
|
||||
|
||||
# https://stackoverflow.com/questions/57591868/how-correctly-install-ssl-certificate-using-certbot-in-docker
|
||||
certbot:
|
||||
image: certbot/certbot:latest
|
||||
container_name: certbot
|
||||
depends_on:
|
||||
- front
|
||||
volumes:
|
||||
- ../certbot/www/:/var/www/certbot/:rw
|
||||
- ../certbot/conf/:/etc/letsencrypt/:rw
|
||||
# certbot:
|
||||
# image: certbot/certbot:latest
|
||||
# container_name: certbot
|
||||
# depends_on:
|
||||
# - front
|
||||
# volumes:
|
||||
# - ../certbot/www/:/var/www/certbot/:rw
|
||||
# - ../certbot/conf/:/etc/letsencrypt/:rw
|
||||
|
||||
back:
|
||||
build:
|
||||
@@ -36,7 +37,7 @@ services:
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ../../../doodlestudent/api:/app
|
||||
- ../doodlestudent/api:/app
|
||||
environment:
|
||||
- FLASK_ENV=development
|
||||
depends_on:
|
||||
@@ -71,7 +72,7 @@ services:
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ../../../doodlestudent/api/APIKEY.txt:/opt/etherpad-lite/APIKEY.txt
|
||||
- ../doodlestudent/api/APIKEY.txt:/opt/etherpad-lite/APIKEY.txt
|
||||
networks:
|
||||
- app-network
|
||||
healthcheck:
|
||||
|
||||
@@ -12,7 +12,7 @@ WORKDIR /app
|
||||
RUN ./mvnw dependency:resolve
|
||||
|
||||
COPY doodlestudent/api/src /app/src
|
||||
RUN ./mvnw package -Pnative -DskipTests
|
||||
RUN ./mvnw package -Pnative -DskipTests -Dquarkus.native.builder-opportunities=2 -Dquarkus.native.native-image-xmx=3g
|
||||
|
||||
RUN ls -l target
|
||||
|
||||
|
||||
22
ansible/files/doodlestudent/README.french.md
Normal file
22
ansible/files/doodlestudent/README.french.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Doodle in quarkus
|
||||
|
||||
Ce repository est une application de type doodle développée avec quarkus.io pour le back et angular pour le front.
|
||||
|
||||
Elle initialise automatiquement un pad pour la réunion et un salon de discussion.
|
||||
|
||||
Le but est de faire travailler les étudiants sur la partie déploiement de ce type d'application dite cloud native.
|
||||
|
||||
Votre mission est de mettre en production une telle application en permettant
|
||||
- qu'à chaque commit sur ce repository, si les tests passent, alors nous déployons automatiquement une nouvelle version dans un contexte (Continuous Deployement)
|
||||
- que l'application doit être monitorer finement.
|
||||
- que l'application redémarre automatiquement en cas de crash du serveur ou de crash d'un des services de l'application.
|
||||
- que Les accès doivent http doivent utiliser https.
|
||||
|
||||
|
||||
Une démo de l'application est accessible [ici](https://doodle.diverse-team.fr).
|
||||
|
||||
- Voici une petite [vidéo](https://drive.google.com/file/d/1GQbdgq2CHcddTlcoHqM5Zc8Dw5o_eeLg/preview) de présentation des fonctionnalités de l'application.
|
||||
- Voici une petite [vidéo](https://drive.google.com/file/d/1l5UAsU5_q-oshwEW6edZ4UvQjN3-tzwi/preview) de présentation de l'architecture de l'application.
|
||||
- Voici une petite [vidéo](https://drive.google.com/file/d/1jxYNfJdtd4r_pDbOthra360ei8Z17tX_/preview) de revue de code de l'application.
|
||||
|
||||
Un descriptif du cours, des TPs et des étapes du projet est lui accessible [ici](https://hackmd.diverse-team.fr/s/SJqu5DjSD)
|
||||
15
ansible/files/doodlestudent/Readme.md
Normal file
15
ansible/files/doodlestudent/Readme.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Remote meetings planning
|
||||
|
||||
This project is used in a course on the *ops* part at the [University of Rennes](https://www.univ-rennes1.fr/), France. It is a kind of doodle clone developed in so-called "native cloud" technologies in order to allow students to work on a continuous deployment chain in a containerized environment. Among the feature, the application automatically initializes a pad for the meeting and a chat room for the meeting participants.
|
||||
|
||||
- The [back](https://github.com/barais/doodlestudent/tree/main/api) is developed using the [quarkus.io](https://quarkus.io/) framework.
|
||||
- The [front](https://github.com/barais/doodlestudent/tree/main/front) is developed in [angular](https://angular.io/) using the [primeng](https://www.primefaces.org/primeng/) angular UI component library and the [fullcalendar](https://fullcalendar.io/) graphical component.
|
||||
|
||||
A demo of the application is available [here](https://doodle.diverse-team.fr/).
|
||||
|
||||
Three videos (in french) are available. They present:
|
||||
- the [main application feature](https://drive.google.com/file/d/1GQbdgq2CHcddTlcoHqM5Zc8Dw5o_eeLg/preview),
|
||||
- its [architecture](https://drive.google.com/file/d/1l5UAsU5_q-oshwEW6edZ4UvQjN3-tzwi/preview)
|
||||
- and a [short code review](https://drive.google.com/file/d/1jxYNfJdtd4r_pDbOthra360ei8Z17tX_/preview) .
|
||||
|
||||
For french native speaker that wants to follow the course. The course web page is available [here](https://hackmd.diverse-team.fr/s/SJqu5DjSD).
|
||||
1
ansible/files/doodlestudent/api/.dockerignore
Normal file
1
ansible/files/doodlestudent/api/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
37
ansible/files/doodlestudent/api/.gitignore
vendored
Normal file
37
ansible/files/doodlestudent/api/.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
#Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
release.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
bin/
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
|
||||
# NetBeans
|
||||
nb-configuration.xml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
.factorypath
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# patch
|
||||
*.orig
|
||||
*.rej
|
||||
|
||||
18
ansible/files/doodlestudent/api/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
18
ansible/files/doodlestudent/api/.mvn/wrapper/maven-wrapper.properties
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
||||
1
ansible/files/doodlestudent/api/APIKEY.txt
Normal file
1
ansible/files/doodlestudent/api/APIKEY.txt
Normal file
@@ -0,0 +1 @@
|
||||
19d89ca52bc0fa4f19d6325464d9d7a032649b9fa68c111514627081e2784b4a
|
||||
53
ansible/files/doodlestudent/api/README.md
Normal file
53
ansible/files/doodlestudent/api/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# code-with-quarkus project
|
||||
|
||||
This project uses Quarkus, the Supersonic Subatomic Java Framework.
|
||||
|
||||
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
|
||||
|
||||
## Running the application in dev mode
|
||||
|
||||
You can run your application in dev mode that enables live coding using:
|
||||
```shell script
|
||||
docker-compose up --detach
|
||||
# Wait the correct start of the docker services and then
|
||||
./mvnw compile quarkus:dev
|
||||
```
|
||||
|
||||
To stop the application and its dependencies, type `ctrl+c` in the bash session and run `docker-compose down`.
|
||||
|
||||
## Packaging and running the application
|
||||
|
||||
The application can be packaged using:
|
||||
```shell script
|
||||
./mvnw package
|
||||
```
|
||||
It produces the `code-with-quarkus-1.0.0-SNAPSHOT-runner.jar` file in the `/target` directory.
|
||||
Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory.
|
||||
If you want to build an _über-jar_, execute the following command:
|
||||
```shell script
|
||||
./mvnw clean package -Dquarkus.package.type=uber-jar
|
||||
```
|
||||
|
||||
The application is now runnable using `java -jar target/code-with-quarkus-1.0.0-SNAPSHOT-runner.jar`.
|
||||
|
||||
## Creating a native executable
|
||||
|
||||
You can create a native executable using:
|
||||
```shell script
|
||||
./mvnw package -Pnative
|
||||
```
|
||||
|
||||
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
|
||||
```shell script
|
||||
./mvnw package -Pnative -Dquarkus.native.container-build=true
|
||||
```
|
||||
|
||||
You can then execute your native executable with: `./target/code-with-quarkus-1.0.0-SNAPSHOT-runner`
|
||||
|
||||
If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling.html.
|
||||
|
||||
# RESTEasy JAX-RS
|
||||
|
||||
Guide: https://quarkus.io/guides/rest-json
|
||||
|
||||
|
||||
22
ansible/files/doodlestudent/api/docker-compose.yaml
Normal file
22
ansible/files/doodlestudent/api/docker-compose.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
db:
|
||||
image: mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=root
|
||||
- MYSQL_DATABASE=tlc
|
||||
- MYSQL_USER=tlc
|
||||
- MYSQL_PASSWORD=tlc
|
||||
etherpad:
|
||||
image: etherpad/etherpad
|
||||
ports:
|
||||
- "9001:9001"
|
||||
volumes:
|
||||
- ./APIKEY.txt:/opt/etherpad-lite/APIKEY.txt
|
||||
mail:
|
||||
image: bytemark/smtp
|
||||
restart: always
|
||||
ports:
|
||||
- "2525:25"
|
||||
308
ansible/files/doodlestudent/api/mvnw
vendored
Executable file
308
ansible/files/doodlestudent/api/mvnw
vendored
Executable file
@@ -0,0 +1,308 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
||||
. /usr/local/etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "$(uname)" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
|
||||
else
|
||||
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=$(java-config --jre-home)
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="$(which javac)"
|
||||
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=$(which readlink)
|
||||
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
|
||||
else
|
||||
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
|
||||
fi
|
||||
javaHome="$(dirname "\"$javaExecutable\"")"
|
||||
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=$(cd "$wdir/.." || exit 1; pwd)
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
# Remove \r in case we run on Windows within Git Bash
|
||||
# and check out the repository with auto CRLF management
|
||||
# enabled. Otherwise, we may read lines that are delimited with
|
||||
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
|
||||
# splitting rules.
|
||||
tr -s '\r\n' ' ' < "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
log() {
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
printf '%s\n' "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
|
||||
log "$MAVEN_PROJECTBASEDIR"
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
if [ -r "$wrapperJarPath" ]; then
|
||||
log "Found $wrapperJarPath"
|
||||
else
|
||||
log "Couldn't find $wrapperJarPath, downloading it ..."
|
||||
|
||||
if [ -n "$MVNW_REPOURL" ]; then
|
||||
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
else
|
||||
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
fi
|
||||
while IFS="=" read -r key value; do
|
||||
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
|
||||
safeValue=$(echo "$value" | tr -d '\r')
|
||||
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
log "Downloading from: $wrapperUrl"
|
||||
|
||||
if $cygwin; then
|
||||
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
|
||||
fi
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
log "Found wget ... using wget"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
else
|
||||
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
elif command -v curl > /dev/null; then
|
||||
log "Found curl ... using curl"
|
||||
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
|
||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
||||
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
else
|
||||
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
else
|
||||
log "Falling back to using Java to download"
|
||||
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
|
||||
# For Cygwin, switch paths to Windows format before running javac
|
||||
if $cygwin; then
|
||||
javaSource=$(cygpath --path --windows "$javaSource")
|
||||
javaClass=$(cygpath --path --windows "$javaClass")
|
||||
fi
|
||||
if [ -e "$javaSource" ]; then
|
||||
if [ ! -e "$javaClass" ]; then
|
||||
log " - Compiling MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/javac" "$javaSource")
|
||||
fi
|
||||
if [ -e "$javaClass" ]; then
|
||||
log " - Running MavenWrapperDownloader.java ..."
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
wrapperSha256Sum=""
|
||||
while IFS="=" read -r key value; do
|
||||
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
|
||||
esac
|
||||
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ -n "$wrapperSha256Sum" ]; then
|
||||
wrapperSha256Result=false
|
||||
if command -v sha256sum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
elif command -v shasum > /dev/null; then
|
||||
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
|
||||
wrapperSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
|
||||
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
|
||||
exit 1
|
||||
fi
|
||||
if [ $wrapperSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
|
||||
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
|
||||
# Provide a "standardized" way to retrieve the CLI args that will
|
||||
# work with both Windows and non-Windows executions.
|
||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
|
||||
export MAVEN_CMD_LINE_ARGS
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
# shellcheck disable=SC2086 # safe args
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
$MAVEN_DEBUG_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
205
ansible/files/doodlestudent/api/mvnw.cmd
vendored
Normal file
205
ansible/files/doodlestudent/api/mvnw.cmd
vendored
Normal file
@@ -0,0 +1,205 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Apache Maven Wrapper startup batch script, version 3.2.0
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Found %WRAPPER_JAR%
|
||||
)
|
||||
) else (
|
||||
if not "%MVNW_REPOURL%" == "" (
|
||||
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
|
||||
)
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %WRAPPER_URL%
|
||||
)
|
||||
|
||||
powershell -Command "&{"^
|
||||
"$webclient = new-object System.Net.WebClient;"^
|
||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
||||
"}"^
|
||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
|
||||
"}"
|
||||
if "%MVNW_VERBOSE%" == "true" (
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
|
||||
SET WRAPPER_SHA_256_SUM=""
|
||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
||||
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
|
||||
)
|
||||
IF NOT %WRAPPER_SHA_256_SUM%=="" (
|
||||
powershell -Command "&{"^
|
||||
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
|
||||
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
|
||||
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
|
||||
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
|
||||
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
|
||||
" exit 1;"^
|
||||
"}"^
|
||||
"}"
|
||||
if ERRORLEVEL 1 goto error
|
||||
)
|
||||
|
||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
||||
@REM work with both Windows and non-Windows executions.
|
||||
set MAVEN_CMD_LINE_ARGS=%*
|
||||
|
||||
%MAVEN_JAVA_EXE% ^
|
||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
||||
%MAVEN_OPTS% ^
|
||||
%MAVEN_DEBUG_OPTS% ^
|
||||
-classpath %WRAPPER_JAR% ^
|
||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
||||
|
||||
cmd /C exit /B %ERROR_CODE%
|
||||
266
ansible/files/doodlestudent/api/pom.xml
Normal file
266
ansible/files/doodlestudent/api/pom.xml
Normal file
@@ -0,0 +1,266 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>fr.istic</groupId>
|
||||
<artifactId>tlcdemoApp</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<properties>
|
||||
<compiler-plugin.version>3.11.0</compiler-plugin.version>
|
||||
<maven.compiler.release>17</maven.compiler.release>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||
<quarkus.platform.version>3.7.1</quarkus.platform.version>
|
||||
<skipITs>true</skipITs>
|
||||
<surefire-plugin.version>3.0.0</surefire-plugin.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${quarkus.platform.group-id}</groupId>
|
||||
<artifactId>${quarkus.platform.artifact-id}</artifactId>
|
||||
<version>${quarkus.platform.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-arc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-orm-rest-data-panache</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-scheduler</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jdbc-mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-metrics</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-opentracing</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-hibernate-validator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-smallrye-openapi</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-mailer</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-flyway</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sourceforge.jexcelapi</groupId>
|
||||
<artifactId>jxl</artifactId>
|
||||
<version>2.6.12</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.gjerull.etherpad</groupId>
|
||||
<artifactId>etherpad_lite_client</artifactId>
|
||||
<version>1.2.13</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mnode.ical4j/ical4j -->
|
||||
<dependency>
|
||||
<groupId>org.mnode.ical4j</groupId>
|
||||
<artifactId>ical4j</artifactId>
|
||||
<version>3.0.20</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpasyncclient -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpasyncclient</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
|
||||
<dependency>
|
||||
<groupId>javax.cache</groupId>
|
||||
<artifactId>cache-api</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.jsr107.ri/cache-ri-impl -->
|
||||
<dependency>
|
||||
<groupId>org.jsr107.ri</groupId>
|
||||
<artifactId>cache-ri-impl</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>commons-logging-jboss-logging</artifactId>
|
||||
<version>1.0.0.Final</version>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logmanager</groupId>
|
||||
<artifactId>log4j-jboss-logmanager</artifactId>
|
||||
<version>1.2.0.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jboss.slf4j</groupId>
|
||||
<artifactId>slf4j-jboss-logging</artifactId>
|
||||
<version>1.2.1.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>${quarkus.platform.group-id}</groupId>
|
||||
<artifactId>quarkus-maven-plugin</artifactId>
|
||||
<version>${quarkus.platform.version}</version>
|
||||
<extensions>true</extensions>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>build</goal>
|
||||
<goal>generate-code</goal>
|
||||
<goal>generate-code-tests</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${surefire-plugin.version}</version>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${surefire-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>native</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>native</name>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>${surefire-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
|
||||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
|
||||
<maven.home>${maven.home}</maven.home>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<quarkus.native.additional-build-args>--initialize-at-run-time=org.apache.http.impl.auth.NTLMEngineImpl,-H:ReflectionConfigurationFiles=reflection-config.json,-H:IncludeResourceBundles=sun.util.resources.TimeZoneNames</quarkus.native.additional-build-args>
|
||||
<skipITs>false</skipITs>
|
||||
<quarkus.package.type>native</quarkus.package.type>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
@@ -0,0 +1,97 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# ./mvnw package
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/tlcdemoApp-jvm .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/tlcdemoApp-jvm
|
||||
#
|
||||
# If you want to include the debug port into your docker image
|
||||
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
|
||||
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
|
||||
# when running the container
|
||||
#
|
||||
# Then run the container using :
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 quarkus/tlcdemoApp-jvm
|
||||
#
|
||||
# This image uses the `run-java.sh` script to run the application.
|
||||
# This scripts computes the command line to execute your Java application, and
|
||||
# includes memory/GC tuning.
|
||||
# You can configure the behavior using the following environment properties:
|
||||
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
|
||||
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
|
||||
# in JAVA_OPTS (example: "-Dsome.property=foo")
|
||||
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
|
||||
# used to calculate a default maximal heap memory based on a containers restriction.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
|
||||
# of the container available memory as set here. The default is `50` which means 50%
|
||||
# of the available memory is used as an upper boundary. You can skip this mechanism by
|
||||
# setting this value to `0` in which case no `-Xmx` option is added.
|
||||
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
|
||||
# is used to calculate a default initial heap memory based on the maximum heap memory.
|
||||
# If used in a container without any memory constraints for the container then this
|
||||
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
|
||||
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
|
||||
# is used as the initial heap size. You can skip this mechanism by setting this value
|
||||
# to `0` in which case no `-Xms` option is added (example: "25")
|
||||
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
|
||||
# This is used to calculate the maximum value of the initial heap memory. If used in
|
||||
# a container without any memory constraints for the container then this option has
|
||||
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
|
||||
# here. The default is 4096MB which means the calculated value of `-Xms` never will
|
||||
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
|
||||
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
|
||||
# when things are happening. This option, if set to true, will set
|
||||
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
|
||||
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
|
||||
# true").
|
||||
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
|
||||
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
|
||||
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
|
||||
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
|
||||
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
|
||||
# (example: "20")
|
||||
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
|
||||
# (example: "40")
|
||||
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
|
||||
# (example: "4")
|
||||
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
|
||||
# previous GC times. (example: "90")
|
||||
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
|
||||
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
|
||||
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
|
||||
# contain the necessary JRE command-line options to specify the required GC, which
|
||||
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
|
||||
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
|
||||
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
|
||||
# accessed directly. (example: "foo.example.com,bar.example.com")
|
||||
#
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi8/openjdk-17:1.16
|
||||
|
||||
ENV LANGUAGE='en_US:en'
|
||||
|
||||
|
||||
# We make four distinct layers so if there are application changes the library layers can be re-used
|
||||
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
|
||||
COPY --chown=185 target/quarkus-app/*.jar /deployments/
|
||||
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
|
||||
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
|
||||
|
||||
EXPOSE 8080
|
||||
USER 185
|
||||
ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
|
||||
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
|
||||
|
||||
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
####
|
||||
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode
|
||||
#
|
||||
# Before building the container image run:
|
||||
#
|
||||
# mvn package -Pnative -Dquarkus.native.container-build=true
|
||||
#
|
||||
# Then, build the image with:
|
||||
#
|
||||
# docker build -f src/main/docker/Dockerfile.native -t barais/doodleback-with-quarkus .
|
||||
#
|
||||
# Then run the container using:
|
||||
#
|
||||
# docker run -i --rm -p 8080:8080 barais/doodleback-with-quarkus
|
||||
#
|
||||
###
|
||||
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
|
||||
WORKDIR /work/
|
||||
RUN chown 1001 /work \
|
||||
&& chmod "g+rwX" /work \
|
||||
&& chown 1001:root /work
|
||||
COPY --chown=1001:root target/*-runner /work/application
|
||||
|
||||
EXPOSE 8080
|
||||
USER 1001
|
||||
|
||||
# ENV quarkus_datasource_jdbc_url "jdbc:mysql://db:3306/tlc?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true&serverTimezone=Europe/Paris"
|
||||
# ENV quarkus_datasource_username tlc
|
||||
# ENV quarkus_datasource_password tlc
|
||||
# ENV quarkus_hibernate_orm_database_generation update
|
||||
# ENV quarkus_mailer_from olivier.barais@gmail.com
|
||||
# ENV quarkus_mailer_host localhost
|
||||
# ENV quarkus_mailer_port 2525
|
||||
# ENV quarkus_mailer_ssl false
|
||||
# ENV quarkus_mailer_username ""
|
||||
# ENV quarkus_mailer_password ""
|
||||
# ENV quarkus_mailer_mock true
|
||||
# ENV doodle_usepad false
|
||||
# ENV doodle_padUrl http://etherpad:9001/
|
||||
# ENV doodle_padApiKey "changeit"
|
||||
# ENV doodle_organizermail "olivier.barais@gmail.com"
|
||||
|
||||
|
||||
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
|
||||
@@ -0,0 +1,12 @@
|
||||
package fr.istic.tlc.dao;
|
||||
|
||||
|
||||
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
|
||||
@ApplicationScoped
|
||||
public class ChoiceRepository implements PanacheRepository<Choice> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.dao;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import fr.istic.tlc.domain.Comment;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
|
||||
@ApplicationScoped
|
||||
public class CommentRepository implements PanacheRepository<Comment> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.dao;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import fr.istic.tlc.domain.MealPreference;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
|
||||
@ApplicationScoped
|
||||
public class MealPreferenceRepository implements PanacheRepository<MealPreference> {
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package fr.istic.tlc.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
|
||||
@ApplicationScoped
|
||||
public class PollRepository implements PanacheRepository<Poll> {
|
||||
|
||||
public Poll findBySlug(String slug){
|
||||
return find("slug", slug).firstResult();
|
||||
}
|
||||
public Poll findByAdminSlug(String slug){
|
||||
return find("slugAdmin", slug).firstResult();
|
||||
}
|
||||
public List<User> findAllUser4Poll(long id){
|
||||
return this.getEntityManager().createQuery("select distinct c.users from Poll p join p.pollChoices as c where p.id = ?1").setParameter(1, id).getResultList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.dao;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
|
||||
import fr.istic.tlc.domain.User;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepository;
|
||||
|
||||
@ApplicationScoped
|
||||
public class UserRepository implements PanacheRepository<User> {
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package fr.istic.tlc.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Temporal;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
@Entity
|
||||
public class Choice {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
//private String name;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date startDate;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date endDate;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "choice_user",
|
||||
joinColumns = @JoinColumn(name = "choice_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "user_id"))
|
||||
private List<User> users;
|
||||
|
||||
|
||||
public Choice(){}
|
||||
|
||||
public Choice(Date startDate, Date endDate, List<User> users) {
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
public void addUser(User user){
|
||||
users.add(user);
|
||||
}
|
||||
|
||||
public void removeUser(User user){
|
||||
users.remove(user);
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getstartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setstartDate(Date startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public Date getendDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setendDate(Date endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
public List<User> getUsers() {
|
||||
return users;
|
||||
}
|
||||
|
||||
public void setUsers(List<User> users) {
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Choice{" +
|
||||
"id=" + id +
|
||||
", startDate=" + startDate +
|
||||
", endDate=" + endDate +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Choice other = (Choice) obj;
|
||||
if (id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!id.equals(other.id))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package fr.istic.tlc.domain;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.Temporal;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
@Entity
|
||||
public class Comment {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String content;
|
||||
private String auteur;
|
||||
|
||||
public Comment(){}
|
||||
|
||||
public Comment(String content){
|
||||
this.content=content;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getAuteur() {
|
||||
return auteur;
|
||||
}
|
||||
|
||||
public void setAuteur(String auteur) {
|
||||
this.auteur = auteur;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Comment{" +
|
||||
"id=" + id +
|
||||
", content='" + content + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package fr.istic.tlc.domain;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
@Entity
|
||||
public class MealPreference {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String content;
|
||||
|
||||
@ManyToOne
|
||||
User user;
|
||||
|
||||
public MealPreference(){}
|
||||
|
||||
public MealPreference(String content){
|
||||
this.content=content;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MealPreference{" +
|
||||
"id=" + id +
|
||||
", content='" + content + '\'' +
|
||||
", user=" + user +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package fr.istic.tlc.domain;
|
||||
|
||||
import fr.istic.tlc.services.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.OrderBy;
|
||||
import jakarta.persistence.Temporal;
|
||||
import jakarta.persistence.TemporalType;
|
||||
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
|
||||
@Entity
|
||||
public class Poll {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String title;
|
||||
private String location;
|
||||
private String description;
|
||||
private boolean has_meal;
|
||||
private String slug = Utils.getInstance().generateSlug(24);
|
||||
private String slugAdmin = Utils.getInstance().generateSlug(24);
|
||||
private String tlkURL = "https://tlk.io/"+Utils.getInstance().generateSlug(12);
|
||||
public boolean clos = false;
|
||||
|
||||
|
||||
@CreationTimestamp
|
||||
private Date createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
private Date updatedAt;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL)
|
||||
@JoinColumn(name = "pollID")
|
||||
@OrderBy("startDate ASC")
|
||||
List<Choice> pollChoices;
|
||||
|
||||
|
||||
@OneToOne(cascade = {CascadeType.PERSIST,CascadeType.REMOVE,CascadeType.REFRESH})
|
||||
Choice selectedChoice;
|
||||
|
||||
@OneToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE,CascadeType.REFRESH})
|
||||
@JoinColumn(name = "pollID")
|
||||
List<Comment> pollComments = new ArrayList<>();
|
||||
|
||||
@OneToMany(cascade = {CascadeType.PERSIST,CascadeType.REMOVE,CascadeType.REFRESH})
|
||||
@JoinColumn(name = "pollID")
|
||||
List<MealPreference> pollMealPreferences = new ArrayList<>();
|
||||
|
||||
private String padURL;
|
||||
|
||||
public Poll(){}
|
||||
|
||||
public Poll(String title, String location, String description, boolean has_meal, List<Choice> pollChoices) {
|
||||
this.title = title;
|
||||
this.location = location;
|
||||
this.description = description;
|
||||
this.has_meal = has_meal;
|
||||
this.pollChoices = pollChoices;
|
||||
}
|
||||
|
||||
public void addChoice(Choice choice){
|
||||
this.pollChoices.add(choice);
|
||||
}
|
||||
|
||||
public void removeChoice(Choice choice){
|
||||
this.pollChoices.remove(choice);
|
||||
}
|
||||
|
||||
public void addComment(Comment comment){ this.pollComments.add(comment);}
|
||||
|
||||
public void removeComment(Comment comment){ this.pollComments.remove(comment);}
|
||||
|
||||
public void addMealPreference(MealPreference mealPreference){ this.pollMealPreferences.add(mealPreference);}
|
||||
|
||||
public void removeComment(MealPreference mealPreference){ this.pollMealPreferences.remove(mealPreference);}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTlkURL() {
|
||||
return tlkURL;
|
||||
}
|
||||
|
||||
public void setTlkURL(String tlkURL) {
|
||||
this.tlkURL = tlkURL;
|
||||
}
|
||||
|
||||
public List<Comment> getPollComments() {
|
||||
return pollComments;
|
||||
}
|
||||
|
||||
public List<MealPreference> getPollMealPreferences() {
|
||||
return pollMealPreferences;
|
||||
}
|
||||
|
||||
public void setPollMealPreferences(List<MealPreference> pollMealPreferences) {
|
||||
this.pollMealPreferences = pollMealPreferences;
|
||||
}
|
||||
|
||||
public void setPollComments(List<Comment> pollComments) {
|
||||
this.pollComments = pollComments;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public void setLocation(String location) {
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public boolean isHas_meal() {
|
||||
return has_meal;
|
||||
}
|
||||
|
||||
public void setHas_meal(boolean has_meal) {
|
||||
this.has_meal = has_meal;
|
||||
}
|
||||
|
||||
public String getSlug() {
|
||||
return slug;
|
||||
}
|
||||
|
||||
public void setSlug(String slug) {
|
||||
this.slug = slug;
|
||||
}
|
||||
|
||||
public String getSlugAdmin() {
|
||||
return slugAdmin;
|
||||
}
|
||||
|
||||
public void setSlugAdmin(String slugAdmin) {
|
||||
this.slugAdmin = slugAdmin;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Date createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public List<Choice> getPollChoices() {
|
||||
return pollChoices;
|
||||
}
|
||||
|
||||
public void setPollChoices(List<Choice> pollChoices) {
|
||||
this.pollChoices = pollChoices;
|
||||
}
|
||||
|
||||
public Choice getSelectedChoice() {
|
||||
return selectedChoice;
|
||||
}
|
||||
|
||||
public void setSelectedChoice(Choice selectedChoice) {
|
||||
this.selectedChoice = selectedChoice;
|
||||
}
|
||||
|
||||
public String getPadURL() {
|
||||
return this.padURL;
|
||||
}
|
||||
|
||||
|
||||
public void setPadURL(String padURL) {
|
||||
this.padURL=padURL;
|
||||
}
|
||||
|
||||
public boolean isClos() {
|
||||
return clos;
|
||||
}
|
||||
|
||||
public void setClos(boolean clos) {
|
||||
this.clos = clos;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Poll{" +
|
||||
"id=" + id +
|
||||
", title='" + title + '\'' +
|
||||
", location='" + location + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", has_meal=" + has_meal +
|
||||
", createdAt=" + createdAt +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Date getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(Date updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package fr.istic.tlc.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.OneToMany;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
private String mail;
|
||||
private String icsurl;
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany(mappedBy = "users")
|
||||
List<Choice> userChoices = new ArrayList<>();
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
@OneToMany(mappedBy="user", cascade = CascadeType.ALL)
|
||||
List<MealPreference> userMealPreferences = new ArrayList<>();
|
||||
|
||||
public User(){}
|
||||
|
||||
public User(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void addChoice(Choice choice){
|
||||
this.userChoices.add(choice);
|
||||
}
|
||||
public String getMail() {
|
||||
return mail;
|
||||
}
|
||||
|
||||
public void setMail(String mail) {
|
||||
this.mail = mail;
|
||||
}
|
||||
|
||||
|
||||
public String getIcsurl() {
|
||||
return icsurl;
|
||||
}
|
||||
|
||||
public void setIcsurl(String icsurl) {
|
||||
this.icsurl = icsurl;
|
||||
}
|
||||
|
||||
public void removeChoice(Choice choice){
|
||||
this.userChoices.remove(choice);
|
||||
}
|
||||
|
||||
|
||||
public void addMealPreference (MealPreference mealPreference) {this.userMealPreferences.add(mealPreference);}
|
||||
|
||||
public void removeMealPreference (MealPreference mealPreference) {this.userMealPreferences.remove(mealPreference);}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public List<Choice> getUserChoices() {
|
||||
return userChoices;
|
||||
}
|
||||
|
||||
public List<MealPreference> getUserMealPreferences() {
|
||||
return userMealPreferences;
|
||||
}
|
||||
|
||||
public void setUserMealPreferences(List<MealPreference> userMealPreferences) {
|
||||
this.userMealPreferences = userMealPreferences;
|
||||
}
|
||||
|
||||
public void setUserChoices(List<Choice> userChoices) {
|
||||
this.userChoices = userChoices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"id=" + id +
|
||||
", username='" + username + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package fr.istic.tlc.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChoiceUser {
|
||||
private List<Long> choices;
|
||||
private String mail;
|
||||
private String pref;
|
||||
private String ics;
|
||||
private String username;
|
||||
|
||||
public List<Long> getChoices() {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public void setChoices(List<Long> value) {
|
||||
this.choices = value;
|
||||
}
|
||||
|
||||
public String getMail() {
|
||||
return mail;
|
||||
}
|
||||
|
||||
public void setMail(String value) {
|
||||
this.mail = value;
|
||||
}
|
||||
|
||||
public String getPref() {
|
||||
return pref;
|
||||
}
|
||||
|
||||
public void setPref(String value) {
|
||||
this.pref = value;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String value) {
|
||||
this.username = value;
|
||||
}
|
||||
|
||||
public String getIcs() {
|
||||
return ics;
|
||||
}
|
||||
|
||||
public void setIcs(String ics) {
|
||||
this.ics = ics;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package fr.istic.tlc.dto;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
|
||||
public class EventDTO {
|
||||
|
||||
private Date startDate;
|
||||
|
||||
private Date endDate;
|
||||
|
||||
private String description;
|
||||
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Date getStartDate() {
|
||||
return startDate;
|
||||
}
|
||||
|
||||
public void setStartDate(Date startDate) {
|
||||
this.startDate = startDate;
|
||||
}
|
||||
|
||||
public Date getEndDate() {
|
||||
return endDate;
|
||||
}
|
||||
|
||||
public void setEndDate(Date endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package fr.istic.tlc.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EventDTOAndSelectedChoice {
|
||||
|
||||
List<EventDTO> eventdtos;
|
||||
List<Long> selectedChoices;
|
||||
public List<EventDTO> getEventdtos() {
|
||||
return eventdtos;
|
||||
}
|
||||
public void setEventdtos(List<EventDTO> eventdtos) {
|
||||
this.eventdtos = eventdtos;
|
||||
}
|
||||
public List<Long> getSelectedChoices() {
|
||||
return selectedChoices;
|
||||
}
|
||||
public void setSelectedChoices(List<Long> selectedChoices) {
|
||||
this.selectedChoices = selectedChoices;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ChoiceResourceEx {
|
||||
|
||||
@Autowired
|
||||
ChoiceRepository choiceRepository;
|
||||
@Autowired
|
||||
PollRepository pollRepository;
|
||||
@Autowired
|
||||
UserRepository userRepository;
|
||||
|
||||
@GetMapping("/polls/{slug}/choices")
|
||||
public ResponseEntity<List<Choice>> retrieveAllChoicesFromPoll(@PathVariable String slug) {
|
||||
// On vérifie que le choix existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
List<Choice> choices = poll.getPollChoices();
|
||||
return new ResponseEntity<>(choices, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/users/{idUser}/choices")
|
||||
public ResponseEntity<List<Choice>> retrieveAllChoicesFromUser(@PathVariable long idUser) {
|
||||
// On vérifie que l'utilisateur existe
|
||||
User user = userRepository.findById(idUser);
|
||||
if (user == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(user.getUserChoices(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/polls/{slug}/choices/{idChoice}")
|
||||
public ResponseEntity<Choice> retrieveChoiceFromPoll(@PathVariable String slug, @PathVariable long idChoice) {
|
||||
// On vérifie que le choix et le poll existent
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
Choice choice = choiceRepository.findById(idChoice);
|
||||
if (poll == null || choice== null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien au poll
|
||||
if(!poll.getPollChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(choice, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/users/{idUser}/choices/{idChoice}")
|
||||
public ResponseEntity<Choice> retrieveChoiceFromUser(@PathVariable long idUser, @PathVariable long idChoice) {
|
||||
// On vérifie que le choix et l'utilisateur existent
|
||||
User user = userRepository.findById(idUser);
|
||||
Choice choice = choiceRepository.findById(idChoice);
|
||||
if (user == null || choice == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien à l'utilisateur
|
||||
if(!user.getUserChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(choice, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping("/polls/{slug}/choices")
|
||||
public ResponseEntity<?> deleteChoiceFromPoll(@RequestBody HashMap<String, List<Long>> choices, @PathVariable String slug, @RequestParam String token) {
|
||||
// On vérifie que le poll existe
|
||||
List<Long> idchoices = choices.get("choices");
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le token soit bon
|
||||
if(!poll.getSlugAdmin().equals(token)){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
// On enlève les choix du poll
|
||||
for (Long id: idchoices) {
|
||||
// On vérifie que le choice existe
|
||||
Choice choice = choiceRepository.findById(id);
|
||||
if (choice!= null) {
|
||||
// On remove le choice du poll
|
||||
poll.removeChoice(choice);
|
||||
pollRepository.getEntityManager().merge(poll);
|
||||
// On remove le choices des utilisateurs
|
||||
for (User user:userRepository.findAll().list()) {
|
||||
if(user.getUserChoices().contains(choice)){
|
||||
user.getUserChoices().remove(choice);
|
||||
userRepository.getEntityManager().merge(user);
|
||||
}
|
||||
}
|
||||
// On supprime le choice
|
||||
choiceRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/polls/{slug}/choices")
|
||||
public ResponseEntity<List<Choice>> createChoices(@RequestBody List<Choice> choices, @PathVariable String slug, @RequestParam String token) {
|
||||
// On vérifie que le poll existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// On vérifie que le token soit bon
|
||||
if(!poll.getSlugAdmin().equals(token)){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
// On ajoute chaque choix au poll et vice versa
|
||||
for (Choice choice:choices) {
|
||||
this.choiceRepository.persist(choice);
|
||||
poll.addChoice(choice);
|
||||
pollRepository.getEntityManager().merge(poll);
|
||||
}
|
||||
return new ResponseEntity<>(choices, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@PutMapping("/polls/{slug}/choices/{idChoice}")
|
||||
public ResponseEntity<Choice> updateChoice(@Valid @RequestBody Choice choice1, @PathVariable String slug, @PathVariable long idChoice, @RequestParam String token) {
|
||||
// On vérifie que le poll et le choix existent
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
Choice choice = choiceRepository.findById(idChoice);
|
||||
if (poll == null || choice == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien au poll
|
||||
if(!poll.getPollChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le token soit bon
|
||||
if(!poll.getSlugAdmin().equals(token)){
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
// On met à jour l'ancien choix
|
||||
Choice ancientChoice = choice;
|
||||
if (choice1.getstartDate()!=null){
|
||||
ancientChoice.setstartDate(choice1.getstartDate());
|
||||
}
|
||||
if (choice1.getendDate()!=null){
|
||||
ancientChoice.setendDate(choice1.getendDate());
|
||||
}
|
||||
// On update la bdd
|
||||
Choice updatedChoice = choiceRepository.getEntityManager().merge(ancientChoice);
|
||||
return new ResponseEntity<>(updatedChoice, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/polls/{slug}/vote/{idUser}")
|
||||
public ResponseEntity<Object> vote(@RequestBody HashMap<String, List<Long>> choices, @PathVariable String slug, @PathVariable long idUser) {
|
||||
// On vérifie que le poll et l'utilisateur existent
|
||||
List<Long> idchoices = choices.get("choices");
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
User user = userRepository.findById(idUser);
|
||||
if (poll == null || user == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
for (Long choice : idchoices) {
|
||||
// On vérifie que le choice existe
|
||||
Choice optchoice = choiceRepository.findById(choice);
|
||||
if (optchoice == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien au poll
|
||||
if(!poll.getPollChoices().contains(optchoice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le user n'ai pas déjà voté pour ce choix
|
||||
if(user.getUserChoices().contains(optchoice)){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// On ajoute le choix à la liste de l'utilisateur et vice versa
|
||||
optchoice.addUser(user);
|
||||
choiceRepository.getEntityManager().merge(optchoice);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/polls/{slug}/choices/{idChoice}/removevote/{idUser}")
|
||||
public ResponseEntity<Object> removeVote(@PathVariable String slug, @PathVariable long idChoice, @PathVariable long idUser) {
|
||||
// On vérifie que le poll, le choix et l'utilisateur existent
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
Choice choice = choiceRepository.findById(idChoice);
|
||||
User user = userRepository.findById(idUser);
|
||||
if (poll == null || choice == null || user == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien au poll
|
||||
if(!poll.getPollChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le user ait bien voté pour ce choix
|
||||
if(!user.getUserChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
// On retire le choix à la liste de l'utilisateur et vice versa
|
||||
choice.removeUser(user);
|
||||
choiceRepository.getEntityManager().merge(choice);
|
||||
user.removeChoice(choice);
|
||||
userRepository.getEntityManager().merge(user);
|
||||
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/polls/{slug}/choices/{idChoice}/count")
|
||||
public ResponseEntity<Object> numberOfVoteForChoice(@PathVariable String slug, @PathVariable long idChoice){
|
||||
// On vérifie que le poll et choix existent
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
Choice choice = choiceRepository.findById(idChoice);
|
||||
if (poll == null || choice == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le choix appartienne bien au poll
|
||||
if(!poll.getPollChoices().contains(choice)){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On compte le nombre de vote pour le choix
|
||||
return new ResponseEntity<>(choice.getUsers().size(),HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
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 fr.istic.tlc.dao.CommentRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.domain.Comment;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class CommentResourceEx {
|
||||
@Autowired
|
||||
PollRepository pollRepository;
|
||||
@Autowired
|
||||
CommentRepository commentRepository;
|
||||
|
||||
/* @GetMapping("users/{idUser}/comments")
|
||||
public ResponseEntity<List<Comment>> getAllCommentsFromUser(@PathVariable long idUser) {
|
||||
// On vérifie que l'utilisateur existe
|
||||
User optUser = userRepository.findById(idUser);
|
||||
if(optUser== null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(optUser.getUserComments(), HttpStatus.OK);
|
||||
}*/
|
||||
|
||||
@GetMapping("polls/{slug}/comments")
|
||||
public ResponseEntity<Object> getAllCommentsFromPoll(@PathVariable String slug) {
|
||||
// On vérifie que le poll existe
|
||||
Poll optPoll = pollRepository.findBySlug(slug);
|
||||
if(optPoll== null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(optPoll.getPollComments(),HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("polls/{slug}/comments/{idComment}")
|
||||
public ResponseEntity<Object> getCommentFromPoll(@PathVariable String slug, @PathVariable long idComment){
|
||||
// On vérifie que le poll et le comment existe
|
||||
Poll optPoll = pollRepository.findBySlug(slug);
|
||||
Comment optComment = commentRepository.findById(idComment);
|
||||
if(optPoll== null || optComment== null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le comment appartienne bien au poll
|
||||
if (!optPoll.getPollComments().contains(optComment)){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return new ResponseEntity<>(optComment,HttpStatus.OK);
|
||||
}
|
||||
|
||||
/* @PostMapping("polls/{slug}/comments/{idUser}")
|
||||
public ResponseEntity<Object> createComment(@Valid @RequestBody Comment comment, @PathVariable String slug, @PathVariable long idUser){
|
||||
// On vérifie que le poll et le User existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
User user = userRepository.findById(idUser);
|
||||
if (poll== null || user== null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On set le user dans comment
|
||||
comment.setUser(user);
|
||||
// On ajoute le commentaire dans le poll
|
||||
poll.addComment(comment);
|
||||
pollRepository.save(poll);
|
||||
// On save le commentaire
|
||||
Comment savedComment = commentRepository.save(comment);
|
||||
return new ResponseEntity<>(savedComment, HttpStatus.CREATED);
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.mail.MessagingException;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
|
||||
import org.apache.http.impl.nio.client.HttpAsyncClients;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.dto.EventDTO;
|
||||
import fr.istic.tlc.dto.EventDTOAndSelectedChoice;
|
||||
import fr.istic.tlc.services.Utils;
|
||||
import net.fortuna.ical4j.data.CalendarBuilder;
|
||||
import net.fortuna.ical4j.data.ParserException;
|
||||
import net.fortuna.ical4j.model.Calendar;
|
||||
import net.fortuna.ical4j.model.Component;
|
||||
import net.fortuna.ical4j.model.ComponentList;
|
||||
import net.fortuna.ical4j.model.DateTime;
|
||||
import net.fortuna.ical4j.model.Period;
|
||||
import net.fortuna.ical4j.model.PeriodList;
|
||||
import net.fortuna.ical4j.model.component.CalendarComponent;
|
||||
import net.fortuna.ical4j.model.component.VEvent;
|
||||
import net.fortuna.ical4j.util.MapTimeZoneCache;
|
||||
|
||||
@Path("/api/ics")
|
||||
public class ICSResources {
|
||||
|
||||
@Inject
|
||||
PollRepository pollRep;
|
||||
|
||||
@Inject
|
||||
UserRepository userRep;
|
||||
|
||||
@Inject
|
||||
ChoiceRepository choiceRep;
|
||||
|
||||
@GET
|
||||
@Path("polls/{slug}/{ics}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public EventDTOAndSelectedChoice parseCalendartoAppointments(@PathParam("slug") String slug,
|
||||
@PathParam("ics") String ics)
|
||||
throws IOException, ParserException, InterruptedException, ExecutionException, MessagingException {
|
||||
EventDTOAndSelectedChoice result = new EventDTOAndSelectedChoice();
|
||||
List<EventDTO> appointments = new ArrayList<>();
|
||||
List<Long> selectedChoices = new ArrayList<>();
|
||||
result.setEventdtos(appointments);
|
||||
result.setSelectedChoices(selectedChoices);
|
||||
|
||||
// Get Poll
|
||||
Poll p = this.pollRep.findBySlug(slug);
|
||||
Date minDate = new Date();
|
||||
if (p != null) {
|
||||
// Get minimal date for Poll to filter ics
|
||||
|
||||
if (p.getPollChoices().size() > 0 && minDate.after(p.getPollChoices().get(0).getstartDate()))
|
||||
minDate = p.getPollChoices().get(0).getstartDate();
|
||||
}
|
||||
|
||||
// Get user to get its ICS
|
||||
// User u = this.userRep.find("mail", usermail).firstResult();
|
||||
byte[] decodedBytes = Base64.getDecoder().decode(ics);
|
||||
String decodedString = new String(decodedBytes);
|
||||
|
||||
if (decodedString != null && !"".equals(decodedString)) {
|
||||
// String s =
|
||||
// "http://zimbra.inria.fr/home/olivier.barais@irisa.fr/Calendar.ics";
|
||||
|
||||
// Query the ics url
|
||||
|
||||
System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName());
|
||||
CloseableHttpAsyncClient client = HttpAsyncClients.createDefault();
|
||||
client.start();
|
||||
HttpGet request = new HttpGet(decodedString);
|
||||
|
||||
Future<HttpResponse> future = client.execute(request, null);
|
||||
HttpResponse response = future.get();
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
response.getEntity().writeTo(out);
|
||||
String responseString = out.toString();
|
||||
out.close();
|
||||
client.close();
|
||||
|
||||
// Parse result
|
||||
StringReader sin = new StringReader(responseString);
|
||||
CalendarBuilder builder = new CalendarBuilder();
|
||||
Calendar calendar = builder.build(sin);
|
||||
ComponentList<CalendarComponent> events = calendar.getComponents(Component.VEVENT);
|
||||
List<Choice> choices = new ArrayList<Choice>();
|
||||
if (p!= null)
|
||||
choices = p.getPollChoices();
|
||||
// Create Event to draw
|
||||
java.util.Calendar calEnd = java.util.Calendar.getInstance();
|
||||
calEnd.setTime(new Date());
|
||||
calEnd.add(java.util.Calendar.YEAR, 1);
|
||||
DateTime start = new DateTime(minDate);
|
||||
DateTime end = new DateTime(calEnd.getTime());
|
||||
for (CalendarComponent event : events) {
|
||||
|
||||
Period period = new Period(start, end);
|
||||
PeriodList list = event.calculateRecurrenceSet(period);
|
||||
for (Period p1 : list) {
|
||||
if (minDate.before(p1.getStart())) {
|
||||
EventDTO a = new EventDTO();
|
||||
a.setStartDate(p1.getStart());
|
||||
a.setEndDate(p1.getEnd());
|
||||
if (((VEvent) event).getSummary() != null)
|
||||
a.setDescription(((VEvent) event).getSummary().getValue());
|
||||
|
||||
// Si intersection ajoute l'ID du choice comme ID selected
|
||||
// https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
|
||||
for (Choice choice : choices) {
|
||||
if (Utils.getInstance().intersect(choice.getstartDate(), choice.getendDate(), p1.getStart(),
|
||||
p1.getEnd())) {
|
||||
if (!selectedChoices.contains(choice.getId())) {
|
||||
selectedChoices.add(choice.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
appointments.add(a);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.MealPreferenceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.MealPreference;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class MealPreferenceResource {
|
||||
@Autowired
|
||||
ChoiceRepository choiceRepository;
|
||||
@Autowired
|
||||
PollRepository pollRepository;
|
||||
@Autowired
|
||||
UserRepository userRepository;
|
||||
@Autowired
|
||||
MealPreferenceRepository mealPreferenceRepository;
|
||||
|
||||
@GetMapping("polls/{slug}/mealpreferences")
|
||||
public ResponseEntity<Object> getAllMealPreferencesFromPoll(@PathVariable String slug) {
|
||||
// On vérifie que le poll existe
|
||||
Poll optPoll = pollRepository.findBySlug(slug);
|
||||
if(optPoll == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(optPoll.getPollMealPreferences(),HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("polls/{slug}/mealpreference/{idMealPreference}")
|
||||
public ResponseEntity<Object> getMealPreferenceFromPoll(@PathVariable String slug, @PathVariable long idMealPreference){
|
||||
// On vérifie que le poll et la meal preference existe
|
||||
Poll optPoll = pollRepository.findBySlug(slug);
|
||||
MealPreference optMealPreference = mealPreferenceRepository.findById(idMealPreference);
|
||||
if(optPoll == null || optMealPreference == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que la meal preference appartienne bien au poll
|
||||
if (!optPoll.getPollMealPreferences().contains(optMealPreference)){
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return new ResponseEntity<>(optMealPreference,HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("polls/{slug}/mealpreference/{idUser}")
|
||||
public ResponseEntity<Object> createMealPreference(@Valid @RequestBody MealPreference mealPreference, @PathVariable String slug, @PathVariable long idUser){
|
||||
// On vérifie que le poll et le User existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
User user = userRepository.findById(idUser);
|
||||
if (poll == null || user == null){
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On set le user dans la meal preference
|
||||
mealPreference.setUser(user);
|
||||
// On ajoute la meal preference dans le poll
|
||||
poll.addMealPreference(mealPreference);
|
||||
// On save la meal preference
|
||||
mealPreferenceRepository.persist(mealPreference);
|
||||
pollRepository.getEntityManager().merge(poll);
|
||||
return new ResponseEntity<>(mealPreference, HttpStatus.CREATED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.CommentRepository;
|
||||
import fr.istic.tlc.dao.MealPreferenceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import fr.istic.tlc.domain.Comment;
|
||||
import fr.istic.tlc.domain.MealPreference;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import fr.istic.tlc.dto.ChoiceUser;
|
||||
import fr.istic.tlc.services.SendEmail;
|
||||
|
||||
@Path("/api/poll")
|
||||
public class NewPollResourceEx {
|
||||
|
||||
@Inject
|
||||
PollRepository pollRep;
|
||||
|
||||
@Inject
|
||||
UserRepository userRep;
|
||||
|
||||
@Inject
|
||||
ChoiceRepository choiceRep;
|
||||
|
||||
@Inject
|
||||
MealPreferenceRepository mealprefRep;
|
||||
|
||||
@Inject
|
||||
CommentRepository commentRep;
|
||||
|
||||
@Inject
|
||||
SendEmail sendmail;
|
||||
|
||||
@Path("/slug/{slug}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Poll getPollBySlug(@PathParam("slug") String slug) {
|
||||
Poll p = pollRep.findBySlug(slug);
|
||||
if (p != null)
|
||||
p.getPollComments().clear();
|
||||
p.setSlugAdmin("");
|
||||
return p;
|
||||
}
|
||||
|
||||
@Path("/aslug/{aslug}")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Poll getPollByASlug(@PathParam("aslug") String aslug) {
|
||||
return pollRep.findByAdminSlug(aslug);
|
||||
}
|
||||
|
||||
@Path("/comment/{slug}")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Comment createComment4Poll(@PathParam("slug") String slug, Comment c) {
|
||||
this.commentRep.persist(c);
|
||||
Poll p = pollRep.findBySlug(slug);
|
||||
p.addComment(c);
|
||||
this.pollRep.persistAndFlush(p);
|
||||
return c;
|
||||
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("/update1")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Poll updatePoll(Poll p) {
|
||||
System.err.println( "p " + p);
|
||||
Poll p1 = pollRep.findById(p.getId());
|
||||
List<Choice> choicesToRemove = new ArrayList<Choice>();
|
||||
for (Choice c : p1.getPollChoices()) {
|
||||
if (!p.getPollChoices().contains(c)) {
|
||||
|
||||
choicesToRemove.add(c);
|
||||
System.err.println("toremove " + c.getId());
|
||||
}
|
||||
|
||||
}
|
||||
for (Choice c : p.getPollChoices()) {
|
||||
if (c.getId() != null) {
|
||||
this.choiceRep.getEntityManager().merge(c);
|
||||
} else {
|
||||
this.choiceRep.getEntityManager().persist(c);
|
||||
}
|
||||
|
||||
}
|
||||
for (Choice c : choicesToRemove) {
|
||||
if (c.equals(p1.getSelectedChoice())) {
|
||||
p.setSelectedChoice(null);
|
||||
p1.setSelectedChoice(null);
|
||||
p.setClos(false);
|
||||
}
|
||||
for (User u : c.getUsers()) {
|
||||
u.getUserChoices().remove(c);
|
||||
}
|
||||
c.getUsers().clear();
|
||||
this.choiceRep.delete(c);
|
||||
|
||||
}
|
||||
|
||||
for (Choice c : p.getPollChoices()) {
|
||||
System.err.println("tomerge " + c.getId());
|
||||
}
|
||||
|
||||
Poll p2 = this.pollRep.getEntityManager().merge(p);
|
||||
return p2;
|
||||
|
||||
}
|
||||
|
||||
@Path("/choiceuser")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
public User addChoiceUser(ChoiceUser userChoice) {
|
||||
User u = this.userRep.find("mail", userChoice.getMail()).firstResult();
|
||||
if (u == null) {
|
||||
u = new User();
|
||||
u.setUsername(userChoice.getUsername());
|
||||
u.setIcsurl(userChoice.getIcs());
|
||||
u.setMail(userChoice.getMail());
|
||||
this.userRep.persist(u);
|
||||
}
|
||||
|
||||
|
||||
if (userChoice.getPref() != null && !"".equals(userChoice.getPref())) {
|
||||
MealPreference mp = new MealPreference();
|
||||
mp.setContent(userChoice.getPref());
|
||||
mp.setUser(u);
|
||||
this.mealprefRep.persist(mp);
|
||||
}
|
||||
for (Long choiceId : userChoice.getChoices()) {
|
||||
Choice c = this.choiceRep.findById(choiceId);
|
||||
c.addUser(u);
|
||||
this.choiceRep.persistAndFlush(c);
|
||||
}
|
||||
return u;
|
||||
}
|
||||
|
||||
@Path("/selectedchoice/{choiceid}")
|
||||
@POST
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Transactional
|
||||
public void closePoll(@PathParam("choiceid") String choiceid) {
|
||||
Choice c = choiceRep.findById(Long.parseLong(choiceid));
|
||||
Poll p = this.pollRep.find("select p from Poll as p join p.pollChoices as c where c.id= ?1", c.getId())
|
||||
.firstResult();
|
||||
p.setClos(true);
|
||||
p.setSelectedChoice(c);
|
||||
this.pollRep.persist(p);
|
||||
this.sendmail.sendASimpleEmail(p);
|
||||
// TODO Send Email
|
||||
|
||||
}
|
||||
|
||||
@GET()
|
||||
@Path("polls/{slug}/comments")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<Comment> getAllCommentsFromPoll(@PathParam("slug") String slug) {
|
||||
Poll p = this.pollRep.findBySlug(slug);
|
||||
if (p!= null)
|
||||
return p.getPollComments();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import fr.istic.tlc.services.Utils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import net.gjerull.etherpad.client.EPLiteClient;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class PollResourceEx {
|
||||
|
||||
@Autowired
|
||||
PollRepository pollRepository;
|
||||
|
||||
@ConfigProperty(name = "doodle.usepad")
|
||||
Boolean usePad = true;
|
||||
@ConfigProperty(name = "doodle.internalPadUrl", defaultValue="http://etherpad:9001/")
|
||||
String padUrl = "";
|
||||
@ConfigProperty(name = "doodle.externalPadUrl", defaultValue="http://etherpad.diverse-team.fr/")
|
||||
String externalPadUrl = "";
|
||||
@ConfigProperty(name = "doodle.padApiKey")
|
||||
String apikey = "";
|
||||
EPLiteClient client;
|
||||
|
||||
@GetMapping("/polls")
|
||||
public ResponseEntity<List<Poll>> retrieveAllpolls() {
|
||||
// On récupère la liste de tous les poll qu'on trie ensuite par titre
|
||||
List<Poll> polls = pollRepository.findAll(Sort.by("title", Sort.Direction.Ascending)).list();
|
||||
return new ResponseEntity<>(polls, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/polls/{slug}")
|
||||
public ResponseEntity<Poll> retrievePoll(@PathVariable String slug, @RequestParam(required = false) String token) {
|
||||
// On vérifie que le poll existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// Si un token est donné, on vérifie qu'il soit bon
|
||||
if (token != null && !poll.getSlugAdmin().equals(token)) {
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
poll.setSlugAdmin("");
|
||||
return new ResponseEntity<>(poll, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/polls/{slug}/pad")
|
||||
public ResponseEntity<String> retrievePadURL(@PathVariable("slug") String slug) {
|
||||
// On vérifie que le poll existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(poll.getPadURL(), HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping("/polls/{slug}")
|
||||
@Transactional
|
||||
public ResponseEntity<Poll> deletePoll(@PathVariable("slug") String slug, @RequestParam String token) {
|
||||
// On vérifie que le poll existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On vérifie que le token soit bon
|
||||
if (!poll.getSlugAdmin().equals(token)) {
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
// On supprime tous les choix du poll
|
||||
// Fait automatiquement par le cascade type ALL
|
||||
|
||||
// On supprime tous les commentaires du poll
|
||||
// Fait automatiquement par le cascade type ALL
|
||||
|
||||
// On supprime le pad
|
||||
if (client == null) {
|
||||
client = new EPLiteClient(padUrl, apikey);
|
||||
}
|
||||
|
||||
client.deletePad(getPadId(poll));
|
||||
// On supprime le poll de la bdd
|
||||
pollRepository.deleteById(poll.getId());
|
||||
return new ResponseEntity<>(poll, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@PostMapping("/polls")
|
||||
@Transactional
|
||||
public ResponseEntity<Poll> createPoll(@Valid @RequestBody Poll poll) {
|
||||
// On enregistre le poll dans la bdd
|
||||
String padId = Utils.getInstance().generateSlug(15);
|
||||
if (this.usePad) {
|
||||
if (client == null) {
|
||||
client = new EPLiteClient(padUrl, apikey);
|
||||
}
|
||||
client.createPad(padId);
|
||||
initPad(poll.getTitle(), poll.getLocation(), poll.getDescription(), client, padId);
|
||||
poll.setPadURL(externalPadUrl + "p/" + padId);
|
||||
}
|
||||
pollRepository.persist(poll);
|
||||
return new ResponseEntity<>(poll, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@PutMapping("/polls/{slug}")
|
||||
@Transactional
|
||||
public ResponseEntity<Object> updatePoll(@Valid @RequestBody Poll poll, @PathVariable String slug,
|
||||
@RequestParam String token) {
|
||||
// On vérifie que le poll existe
|
||||
Poll optionalPoll = pollRepository.findBySlug(slug);
|
||||
if (optionalPoll == null)
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
// On vérifie que le token soit bon
|
||||
if (!optionalPoll.getSlugAdmin().equals(token)) {
|
||||
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
// On met au poll le bon id et les bons slugs
|
||||
Poll ancientPoll = optionalPoll;
|
||||
// On se connecte au pad
|
||||
String padId = getPadId(ancientPoll);
|
||||
|
||||
// On sauvegarde les anciennes données pour mettre à jour le pad
|
||||
String title = ancientPoll.getTitle();
|
||||
String location = ancientPoll.getLocation();
|
||||
String description = ancientPoll.getDescription();
|
||||
|
||||
// On met à jour l'ancien poll
|
||||
if (poll.getTitle() != null) {
|
||||
ancientPoll.setTitle(poll.getTitle());
|
||||
}
|
||||
if (poll.getLocation() != null) {
|
||||
ancientPoll.setLocation(poll.getLocation());
|
||||
}
|
||||
if (poll.getDescription() != null) {
|
||||
ancientPoll.setDescription(poll.getDescription());
|
||||
}
|
||||
ancientPoll.setHas_meal(poll.isHas_meal());
|
||||
// On update le pad
|
||||
String ancientPad = (String) client.getText(padId).get("text");
|
||||
ancientPad = ancientPad.replaceFirst(title, ancientPoll.getTitle());
|
||||
ancientPad = ancientPad.replaceFirst(location, ancientPoll.getLocation());
|
||||
ancientPad = ancientPad.replaceFirst(description, ancientPoll.getDescription());
|
||||
client.setText(padId, ancientPad);
|
||||
// On enregistre le poll dans la bdd
|
||||
Poll updatedPoll = pollRepository.getEntityManager().merge(ancientPoll);
|
||||
return new ResponseEntity<>(updatedPoll, HttpStatus.OK);
|
||||
}
|
||||
|
||||
private static void initPad(String pollTitle, String pollLocation, String pollDescription, EPLiteClient client,
|
||||
String padId) {
|
||||
final String str = pollTitle + '\n' + "Localisation : " + pollLocation + '\n' + "Description : "
|
||||
+ pollDescription + '\n';
|
||||
client.setText(padId, str);
|
||||
}
|
||||
|
||||
private static String getPadId(Poll poll) {
|
||||
//return poll.getPadURL().substring(poll.getPadURL().length() - 6);
|
||||
return poll.getPadURL().substring(poll.getPadURL().lastIndexOf('/') + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package fr.istic.tlc.resources;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class UserResource {
|
||||
|
||||
@Autowired
|
||||
ChoiceRepository choiceRepository;
|
||||
@Autowired
|
||||
PollRepository pollRepository;
|
||||
@Autowired
|
||||
UserRepository userRepository;
|
||||
|
||||
@GetMapping("/users")
|
||||
public ResponseEntity<List<User>> retrieveAllUsers() {
|
||||
// On récupère tous les utilisateurs qu'on trie ensuite par username
|
||||
List<User> users = userRepository.findAll(Sort.by( "username", Sort.Direction.Ascending)).list();
|
||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/users/{idUser}")
|
||||
public ResponseEntity<User> retrieveUser(@PathVariable long idUser) {
|
||||
// On vérifie que l'utilisateur existe
|
||||
User user = userRepository.findById(idUser);
|
||||
if (user== null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
return new ResponseEntity<>(user, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/polls/{slug}/users")
|
||||
public ResponseEntity<List<User>> getAllUserFromPoll(@PathVariable String slug) {
|
||||
List<User> users = new ArrayList<>();
|
||||
// On vérifie que le poll existe
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll== null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On parcours les choix du poll pour récupérer les users ayant voté
|
||||
if (!poll.getPollChoices().isEmpty()) {
|
||||
for (Choice choice : poll.getPollChoices()) {
|
||||
if (!choice.getUsers().isEmpty()) {
|
||||
for (User user : choice.getUsers()) {
|
||||
// On vérifie que le user ne soit pas déjà dans la liste
|
||||
if (!users.contains(user)) {
|
||||
users.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@DeleteMapping("/users/{idUser}")
|
||||
public ResponseEntity<User> deleteUser(@PathVariable long idUser) {
|
||||
// On vérifie que l'utilisateur existe
|
||||
User user = userRepository.findById(idUser);
|
||||
if (user== null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On supprime l'utilisateur de la liste d'utilisateur de chaque choix
|
||||
for (Choice choice : user.getUserChoices()) {
|
||||
choice.removeUser(user);
|
||||
choiceRepository.getEntityManager().merge(choice);
|
||||
}
|
||||
// On supprime les commentaires de l'utilisateurs
|
||||
// Fait automatiquement par le cascade type ALL
|
||||
|
||||
// On supprime l'utilisateur de la bdd
|
||||
userRepository.deleteById(idUser);
|
||||
return new ResponseEntity<>(user, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/users")
|
||||
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
|
||||
// On sauvegarde l'utilisateur dans la bdd
|
||||
userRepository.persist(user);
|
||||
return new ResponseEntity<>(user, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
@PutMapping("/users/{idUser}")
|
||||
public ResponseEntity<User> updateUser(@PathVariable long idUser, @Valid @RequestBody User user) {
|
||||
// On vérifie que l'utilisateur existe
|
||||
User optionalUser = userRepository.findById(idUser);
|
||||
if (optionalUser== null) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
// On met le bon id sur l'utilisateur
|
||||
User ancientUser = optionalUser;
|
||||
if (user.getUsername() != null) {
|
||||
ancientUser.setUsername(user.getUsername());
|
||||
}
|
||||
// On update l'utilisateur dans la bdd
|
||||
User updatedUser = userRepository.getEntityManager().merge(ancientUser);
|
||||
return new ResponseEntity<>(updatedUser, HttpStatus.OK);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package fr.istic.tlc.resources.features;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jxl.Workbook;
|
||||
import jxl.format.Alignment;
|
||||
import jxl.format.Border;
|
||||
import jxl.format.BorderLineStyle;
|
||||
import jxl.format.Colour;
|
||||
import jxl.format.VerticalAlignment;
|
||||
import jxl.write.Label;
|
||||
import jxl.write.Number;
|
||||
import jxl.write.WritableCellFormat;
|
||||
import jxl.write.WritableFont;
|
||||
import jxl.write.WritableSheet;
|
||||
import jxl.write.WritableWorkbook;
|
||||
|
||||
@Path("/api")
|
||||
public class ExportResource {
|
||||
@Inject
|
||||
ChoiceRepository choiceRepository;
|
||||
@Inject
|
||||
PollRepository pollRepository;
|
||||
@Inject
|
||||
UserRepository userRepository;
|
||||
|
||||
|
||||
@ConfigProperty(name = "doodle.tmpfolder")
|
||||
String EXCEL_FILE_LOCATION = "/tmp/excelFiles";
|
||||
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
@Path("/polls/{slug}/results")
|
||||
public Response downloadResultsExcel(@PathParam("slug") String slug) throws IOException {
|
||||
Poll poll = pollRepository.findBySlug(slug);
|
||||
if (poll == null) {
|
||||
return null;
|
||||
}
|
||||
String filePath = createExcelFile(poll, slug);
|
||||
return getHttpEntityToDownload(filePath, "vnd.ms-excel");
|
||||
}
|
||||
|
||||
/*
|
||||
* @RequestMapping(value = "/polls/{slug}/print", method = RequestMethod.GET,
|
||||
* produces = APPLICATION_PDF) public @ResponseBody HttpEntity<byte[]>
|
||||
* downloadResultsPdf(@PathVariable String slug) throws IOException { Poll poll
|
||||
* = pollRepository.findBySlug(slug); if (poll == null) { return new
|
||||
* ResponseEntity<>(HttpStatus.NOT_FOUND); } String filePath = "./Test.xls";
|
||||
* //Utils.excel2pdf(); //convertToPdf(filePath); return
|
||||
* getHttpEntityToDownload(filePath,"pdf"); }
|
||||
*/
|
||||
|
||||
/*
|
||||
* private String convertToPdf(String filePath){ return "";
|
||||
*
|
||||
* }
|
||||
*/
|
||||
|
||||
static int beginningColumnCell = 0;
|
||||
static int beginningRowCell = 3;
|
||||
static int fontSize = 9;
|
||||
static Colour borderColour = Colour.WHITE;
|
||||
|
||||
private String createExcelFile(Poll poll, String slug) throws IOException {
|
||||
DateFormat dateFormat = new SimpleDateFormat("dd.MM.yy-HH.mm.ss");
|
||||
Date date = new Date();
|
||||
String fileName = EXCEL_FILE_LOCATION + File.separator + slug + "-" + dateFormat.format(date) + ".xls";
|
||||
|
||||
File folder = new File(EXCEL_FILE_LOCATION);
|
||||
if (!folder.exists()) {
|
||||
folder.mkdir();
|
||||
}
|
||||
|
||||
// Create an Excel file
|
||||
WritableWorkbook Wbook = null;
|
||||
try {
|
||||
System.out.println("Création du fichier");
|
||||
// Create an Excel file in the file location
|
||||
File file = new File(fileName);
|
||||
|
||||
if (!file.createNewFile()) {
|
||||
System.out.println("Erreur lors de la création du fichier");
|
||||
}
|
||||
Wbook = Workbook.createWorkbook(file);
|
||||
|
||||
// Create an Excel sheet
|
||||
WritableSheet mainSheet = Wbook.createSheet("SONDAGE", 0);
|
||||
Wbook.setColourRGB(Colour.BLUE, 53, 37, 230);
|
||||
|
||||
// Format objects
|
||||
WritableCellFormat formatTitle = new WritableCellFormat();
|
||||
WritableFont fontTitle = new WritableFont(WritableFont.TAHOMA, 16, WritableFont.BOLD);
|
||||
fontTitle.setColour(Colour.BLUE);
|
||||
formatTitle.setFont(fontTitle);
|
||||
|
||||
Label label;
|
||||
label = new Label(0, 0, "Sondage \"" + poll.getTitle() + "\"", formatTitle);
|
||||
mainSheet.addCell(label);
|
||||
label = new Label(0, 1, "http://localhost:3000/polls/" + poll.getSlug());
|
||||
mainSheet.addCell(label);
|
||||
|
||||
// On récupere les users qui ont voté dans ce sondage
|
||||
List<User> users = retrieveUsers(poll);
|
||||
|
||||
// On ecrit les users sur la première colonne
|
||||
writeUsers(poll, Wbook, users);
|
||||
|
||||
// On ecrit les choix avec les votes de chaque users
|
||||
writeChoices(poll, Wbook, users);
|
||||
|
||||
System.out.println("Enregistrement du fichier");
|
||||
// On ecrit les donnée du workbook dans un format excel
|
||||
Wbook.write();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.out.println("Erreur lors de la création du fichier :( " + e.toString());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
|
||||
if (Wbook != null) {
|
||||
try {
|
||||
Wbook.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private List<User> retrieveUsers(Poll poll) {
|
||||
List<User> users = new ArrayList<>();
|
||||
// On parcours les choix du poll pour récupérer les users ayant voté
|
||||
if (!poll.getPollChoices().isEmpty()) {
|
||||
for (Choice choice : poll.getPollChoices()) {
|
||||
if (!choice.getUsers().isEmpty()) {
|
||||
for (User user : choice.getUsers()) {
|
||||
// On vérifie que le user ne soit pas déjà dans la liste
|
||||
if (!users.contains(user)) {
|
||||
users.add(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
private void writeChoices(Poll poll, WritableWorkbook Wbook, List<User> users) throws jxl.write.WriteException {
|
||||
Label label;
|
||||
Number number;
|
||||
WritableSheet mainSheet = Wbook.getSheet(0);
|
||||
|
||||
List<fr.istic.tlc.domain.Choice> choices = poll.getPollChoices();
|
||||
// List<Choice> choices =
|
||||
// choiceRepository.findAll(Sort.by(Sort.Direction.ASC,"startDate"));
|
||||
|
||||
// Format objects
|
||||
WritableCellFormat formatVoteYes = new WritableCellFormat();
|
||||
formatVoteYes.setAlignment(Alignment.CENTRE);
|
||||
formatVoteYes.setVerticalAlignment(VerticalAlignment.CENTRE);
|
||||
formatVoteYes.setBorder(Border.ALL, BorderLineStyle.THIN, borderColour);
|
||||
formatVoteYes.setBackground(Colour.LIGHT_GREEN);
|
||||
WritableFont fontVoteYes = new WritableFont(WritableFont.TAHOMA, fontSize, WritableFont.NO_BOLD);
|
||||
fontVoteYes.setColour(Colour.BLACK);
|
||||
formatVoteYes.setFont(fontVoteYes);
|
||||
// Format objects
|
||||
Wbook.setColourRGB(Colour.LIGHT_ORANGE, 255, 195, 195);
|
||||
WritableCellFormat formatVoteNo = new WritableCellFormat();
|
||||
formatVoteNo.setAlignment(Alignment.CENTRE);
|
||||
formatVoteNo.setVerticalAlignment(VerticalAlignment.CENTRE);
|
||||
formatVoteNo.setBorder(Border.ALL, BorderLineStyle.THIN, borderColour);
|
||||
formatVoteNo.setBackground(Colour.LIGHT_ORANGE);
|
||||
WritableFont fontVoteNo = new WritableFont(WritableFont.TAHOMA, fontSize, WritableFont.NO_BOLD);
|
||||
fontVoteNo.setColour(Colour.BLACK);
|
||||
formatVoteNo.setFont(fontVoteNo);
|
||||
|
||||
// On ecrit les colonnes des choix
|
||||
for (int i = 0; i < choices.size(); i++) {
|
||||
mainSheet.setColumnView(1 + beginningColumnCell + i, 14);
|
||||
// On ecrit la date
|
||||
writeChoiceDate(Wbook, choices, i);
|
||||
|
||||
// On ecrit les votes
|
||||
List<User> listUsersVotes = choices.get(i).getUsers();
|
||||
for (int x = 0; x < users.size(); x++) {
|
||||
if (listUsersVotes.contains(users.get(x))) {
|
||||
label = new Label(1 + beginningColumnCell + i, 3 + beginningRowCell + x, "OK", formatVoteYes);
|
||||
} else {
|
||||
label = new Label(1 + beginningColumnCell + i, 3 + beginningRowCell + x, "-", formatVoteNo);
|
||||
}
|
||||
mainSheet.addCell(label);
|
||||
}
|
||||
// on ecrit le nombre total de vote pour le choix
|
||||
number = new Number(1 + beginningColumnCell + i, 3 + beginningRowCell + users.size(),
|
||||
listUsersVotes.size());
|
||||
mainSheet.addCell(number);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void writeChoiceDate(WritableWorkbook Wbook, List<Choice> choices, int i) throws jxl.write.WriteException {
|
||||
Label label;
|
||||
WritableSheet mainSheet = Wbook.getSheet(0);
|
||||
String month[] = { "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre",
|
||||
"Novembre", "Décembre" };
|
||||
String dayOfWeek[] = { "Lun.", "Mar.", "Mer.", "Jeu.", "Ven.", "Sam.", "Dim." };
|
||||
Wbook.setColourRGB(Colour.BLUE, 53, 37, 230);
|
||||
// Format objects
|
||||
WritableCellFormat formatDate = new WritableCellFormat();
|
||||
formatDate.setAlignment(Alignment.CENTRE);
|
||||
formatDate.setVerticalAlignment(VerticalAlignment.CENTRE);
|
||||
formatDate.setBorder(Border.ALL, BorderLineStyle.THIN, borderColour);
|
||||
formatDate.setBackground(Colour.BLUE);
|
||||
WritableFont fontDate = new WritableFont(WritableFont.TAHOMA, fontSize, WritableFont.NO_BOLD);
|
||||
fontDate.setColour(Colour.WHITE);
|
||||
formatDate.setFont(fontDate);
|
||||
|
||||
// On recupère la date de début
|
||||
Choice choice = choices.get(i);
|
||||
Date startDate = choice.getstartDate();
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(startDate);
|
||||
int startYear = calendar.get(Calendar.YEAR);
|
||||
String startMonth = month[calendar.get(Calendar.MONTH)];
|
||||
int startDayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
|
||||
String startDayOfWeek = dayOfWeek[calendar.get(Calendar.DAY_OF_WEEK) - 1];
|
||||
int startHourInt = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
String startHour = (startHourInt < 10 ? "0" : "") + startHourInt;
|
||||
int startMinuteInt = calendar.get(Calendar.MINUTE);
|
||||
String startMinute = (startMinuteInt < 10 ? "0" : "") + startMinuteInt;
|
||||
// On recupère la date de fin
|
||||
Date endDate = choice.getendDate();
|
||||
calendar.setTime(endDate);
|
||||
int endHourInt = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
String endHour = (endHourInt < 10 ? "0" : "") + endHourInt;
|
||||
int endMinuteInt = calendar.get(Calendar.MINUTE);
|
||||
String endMinute = (endMinuteInt < 10 ? "0" : "") + endMinuteInt;
|
||||
|
||||
label = new Label(1 + beginningColumnCell + i, beginningRowCell, startMonth + " " + startYear, formatDate);
|
||||
mainSheet.addCell(label);
|
||||
label = new Label(1 + beginningColumnCell + i, 1 + beginningRowCell, startDayOfWeek + " " + startDayOfMonth,
|
||||
formatDate);
|
||||
mainSheet.addCell(label);
|
||||
label = new Label(1 + beginningColumnCell + i, 2 + beginningRowCell,
|
||||
startHour + ":" + startMinute + " - " + endHour + ":" + endMinute, formatDate);
|
||||
mainSheet.addCell(label);
|
||||
}
|
||||
|
||||
private void writeUsers(Poll poll, WritableWorkbook Wbook, List<User> users) throws jxl.write.WriteException {
|
||||
Label label;
|
||||
|
||||
WritableSheet mainSheet = Wbook.getSheet(0);
|
||||
mainSheet.setColumnView(beginningColumnCell, 25);
|
||||
|
||||
// Format objects
|
||||
WritableCellFormat formatUser = new WritableCellFormat();
|
||||
formatUser.setAlignment(Alignment.RIGHT);
|
||||
formatUser.setVerticalAlignment(VerticalAlignment.CENTRE);
|
||||
formatUser.setBorder(Border.ALL, BorderLineStyle.THIN, borderColour);
|
||||
|
||||
formatUser.setBackground(Colour.GRAY_25);
|
||||
WritableFont fontUser = new WritableFont(WritableFont.TAHOMA, fontSize, WritableFont.NO_BOLD);
|
||||
fontUser.setColour(Colour.BLACK);
|
||||
formatUser.setFont(fontUser);
|
||||
|
||||
// On ecrit la premier colonne avec users et label "Nombre"
|
||||
for (int i = 0; i < users.size(); i++) {
|
||||
label = new Label(beginningColumnCell, 3 + beginningRowCell + i, users.get(i).getUsername(), formatUser);
|
||||
mainSheet.addCell(label);
|
||||
}
|
||||
label = new Label(beginningColumnCell, 3 + beginningRowCell + users.size(), "Nombre");
|
||||
mainSheet.addCell(label);
|
||||
}
|
||||
|
||||
private Response getHttpEntityToDownload(String filePath, String fileType) throws IOException {
|
||||
File file = getFile(filePath);
|
||||
|
||||
// header.set("Content-Disposition", "inline; filename=" + file.getName());
|
||||
return Response.ok(((Object) file), MediaType.APPLICATION_OCTET_STREAM)
|
||||
.header("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"").build();
|
||||
}
|
||||
|
||||
private File getFile(String filePath) throws FileNotFoundException {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists()) {
|
||||
throw new FileNotFoundException("file with path: " + filePath + " was not found.");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/*
|
||||
* @ControllerAdvice public class GlobalExceptionHandler {
|
||||
*
|
||||
* @ExceptionHandler(value = FileNotFoundException.class) public void
|
||||
* handle(FileNotFoundException ex, HttpServletResponse response) throws
|
||||
* IOException { System.out.println("handling file not found exception");
|
||||
* response.sendError(404, ex.getMessage()); }
|
||||
*
|
||||
* @ExceptionHandler(value = IOException.class) public void handle(IOException
|
||||
* ex, HttpServletResponse response) throws IOException {
|
||||
* System.out.println("handling io exception"); response.sendError(500,
|
||||
* ex.getMessage()); } }
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package fr.istic.tlc.resources.itf;
|
||||
|
||||
|
||||
import fr.istic.tlc.dao.ChoiceRepository;
|
||||
import fr.istic.tlc.domain.Choice;
|
||||
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
|
||||
import io.quarkus.rest.data.panache.ResourceProperties;
|
||||
|
||||
@ResourceProperties(path = "/api/choice")
|
||||
public interface ChoiceResource extends PanacheRepositoryResource<ChoiceRepository, Choice,Long> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.resources.itf;
|
||||
|
||||
import fr.istic.tlc.dao.CommentRepository;
|
||||
import fr.istic.tlc.domain.Comment;
|
||||
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
|
||||
import io.quarkus.rest.data.panache.ResourceProperties;
|
||||
|
||||
@ResourceProperties(path = "/api/comment")
|
||||
public interface CommentResource extends PanacheRepositoryResource<CommentRepository, Comment,Long> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.resources.itf;
|
||||
|
||||
import fr.istic.tlc.dao.MealPreferenceRepository;
|
||||
import fr.istic.tlc.domain.MealPreference;
|
||||
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
|
||||
import io.quarkus.rest.data.panache.ResourceProperties;
|
||||
|
||||
@ResourceProperties(path = "/api/mealpreference")
|
||||
public interface MealPreferenceResource extends PanacheRepositoryResource<MealPreferenceRepository,MealPreference,Long> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.resources.itf;
|
||||
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
|
||||
import io.quarkus.rest.data.panache.ResourceProperties;
|
||||
|
||||
@ResourceProperties(path = "/api/poll")
|
||||
public interface PollResource extends PanacheRepositoryResource<PollRepository, Poll,Long> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package fr.istic.tlc.resources.itf;
|
||||
|
||||
import fr.istic.tlc.dao.UserRepository;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
|
||||
import io.quarkus.rest.data.panache.ResourceProperties;
|
||||
|
||||
@ResourceProperties(path = "/api/user")
|
||||
public interface UserResource extends PanacheRepositoryResource<UserRepository, User,Long> {
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package fr.istic.tlc.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty;
|
||||
|
||||
import fr.istic.tlc.dao.PollRepository;
|
||||
import fr.istic.tlc.domain.Poll;
|
||||
import fr.istic.tlc.domain.User;
|
||||
import io.quarkus.mailer.Mail;
|
||||
import io.quarkus.mailer.Mailer;
|
||||
import net.fortuna.ical4j.model.DateTime;
|
||||
import net.fortuna.ical4j.model.TimeZone;
|
||||
import net.fortuna.ical4j.model.TimeZoneRegistry;
|
||||
import net.fortuna.ical4j.model.TimeZoneRegistryFactory;
|
||||
import net.fortuna.ical4j.model.component.VEvent;
|
||||
import net.fortuna.ical4j.model.component.VTimeZone;
|
||||
import net.fortuna.ical4j.model.parameter.Role;
|
||||
import net.fortuna.ical4j.model.property.Attendee;
|
||||
import net.fortuna.ical4j.model.property.CalScale;
|
||||
import net.fortuna.ical4j.model.property.Method;
|
||||
import net.fortuna.ical4j.model.property.Organizer;
|
||||
import net.fortuna.ical4j.model.property.ProdId;
|
||||
import net.fortuna.ical4j.model.property.Uid;
|
||||
import net.fortuna.ical4j.model.property.Version;
|
||||
import net.fortuna.ical4j.util.MapTimeZoneCache;
|
||||
import net.fortuna.ical4j.util.RandomUidGenerator;
|
||||
import net.fortuna.ical4j.util.UidGenerator;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SendEmail {
|
||||
|
||||
@Inject
|
||||
Mailer mailer;
|
||||
|
||||
@Inject
|
||||
PollRepository pollRep;
|
||||
|
||||
@ConfigProperty(name = "doodle.organizermail")
|
||||
String organizermail= "test@test.fr";
|
||||
public void sendASimpleEmail(Poll p ) {
|
||||
// Create a default MimeMessage object.
|
||||
System.setProperty("net.fortuna.ical4j.timezone.cache.impl", MapTimeZoneCache.class.getName());
|
||||
|
||||
List<User> u = this.pollRep.findAllUser4Poll(p.getId());
|
||||
List<String> attendees = new ArrayList<String>();
|
||||
for (User u1 : u) {
|
||||
attendees.add(u1.getMail());
|
||||
}
|
||||
|
||||
String ics = this.getICS1(p.getSelectedChoice().getstartDate(), p.getSelectedChoice().getendDate(), p.getTitle(), attendees, organizermail);
|
||||
Mail m = new Mail();
|
||||
m.addAttachment("meeting.ics", ics.getBytes(), "text/calendar");
|
||||
|
||||
m.setFrom(organizermail);
|
||||
m.setTo(attendees);
|
||||
m.setCc(Arrays.asList(organizermail));
|
||||
m.setFrom(organizermail);
|
||||
m.setSubject("Réunion c" + p.getTitle() + " [créneau confirmé] ");
|
||||
m.setHtml("La date définitive pour la réunion : \""+ p.getTitle() + "\" a été validée par l\'organisateur. <BR>" +
|
||||
"Un salon a été créé de discussion pour cette réunion est accessible à cette adresse <a [href]=\" " +p.getTlkURL() + "\" target=\"_blank\">" + p.getTlkURL() + "</a>.<BR>\n" +
|
||||
"Un pad a été créé pour cette réunion <a [href]=\""+ p.getPadURL() + "\" target=\"_blank\">\""+ p.getPadURL() + "\"</a>.</span><BR>\n");
|
||||
|
||||
mailer.send(m);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public String getICS1(Date start, Date end, String libelle, List<String> attendees, String organizer) {
|
||||
|
||||
// Create a TimeZone
|
||||
TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry();
|
||||
TimeZone timezone = registry.getTimeZone("Europe/Paris");
|
||||
VTimeZone tz = timezone.getVTimeZone();
|
||||
|
||||
// Create the event
|
||||
DateTime startd = new DateTime(start);
|
||||
DateTime endd = new DateTime(end);
|
||||
VEvent meeting = new VEvent(startd, endd, libelle);
|
||||
// add timezone info..
|
||||
meeting.getProperties().add(tz.getTimeZoneId());
|
||||
|
||||
// generate unique identifier..
|
||||
UidGenerator ug = new RandomUidGenerator();
|
||||
Uid uid = ug.generateUid();
|
||||
meeting.getProperties().add(uid);
|
||||
|
||||
|
||||
// add attendees..
|
||||
for (String attendee : attendees) {
|
||||
Attendee p1 = new Attendee(URI.create("mailto:"+attendee));
|
||||
p1.getParameters().add(Role.REQ_PARTICIPANT);
|
||||
// dev1.getParameters().add(new Cn("Developer 1"));
|
||||
meeting.getProperties().add(p1);
|
||||
}
|
||||
Organizer p1 = new Organizer(URI.create("mailto:"+organizer));
|
||||
meeting.getProperties().add(p1);
|
||||
|
||||
|
||||
// Create a calendar
|
||||
net.fortuna.ical4j.model.Calendar icsCalendar = new net.fortuna.ical4j.model.Calendar();
|
||||
icsCalendar.getProperties().add(Version.VERSION_2_0);
|
||||
icsCalendar.getProperties().add(new ProdId("Zimbra-Calendar-Provider"));
|
||||
icsCalendar.getProperties().add(CalScale.GREGORIAN);
|
||||
icsCalendar.getProperties().add(Method.REQUEST);
|
||||
icsCalendar.getComponents().add(tz);
|
||||
|
||||
// Add the event and print
|
||||
icsCalendar.getComponents().add(meeting);
|
||||
|
||||
return icsCalendar.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package fr.istic.tlc.services;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
|
||||
public class Utils {
|
||||
private Random random = null;// = new Random();
|
||||
private static final String CHARS = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ234567890";
|
||||
private static Utils instance = null;
|
||||
private Utils(){
|
||||
this.random = new Random();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static Utils getInstance(){
|
||||
if (instance == null)
|
||||
instance = new Utils();
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
public String generateSlug(int length) {
|
||||
if (random == null){
|
||||
random = new Random();
|
||||
}
|
||||
StringBuilder slug = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
slug.append(CHARS.charAt(random.nextInt(CHARS.length())));
|
||||
}
|
||||
return slug.toString();
|
||||
}
|
||||
|
||||
public boolean intersect(Date start1, Date end1, Date start2, Date end2) {
|
||||
if (start1 == null || start2 == null ||end1 == null||end2 == null)
|
||||
return false;
|
||||
return end1.after(start2) && start1.before(end2);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,34 @@
|
||||
doodle:
|
||||
usepad: true
|
||||
internalPadUrl: "http://localhost:9001/"
|
||||
externalPadUrl: "http://localhost:9001/"
|
||||
padApiKey: "19d89ca52bc0fa4f19d6325464d9d7a032649b9fa68c111514627081e2784b4a"
|
||||
organizermail: "olivier.barais@gmail.com"
|
||||
tmpfolder: "/tmp/excelFiles"
|
||||
quarkus:
|
||||
datasource:
|
||||
db-kind: mysql
|
||||
username: tlc
|
||||
password: tlc
|
||||
jdbc:
|
||||
url: jdbc:mysql://localhost:3306/tlc?useUnicode=true&serverTimezone=Europe/Paris
|
||||
driver: com.mysql.cj.jdbc.Driver
|
||||
hibernate-orm:
|
||||
validate-in-dev-mode: false
|
||||
# flyway:
|
||||
# migrate-at-start: true
|
||||
# baseline-on-migrate: true
|
||||
database:
|
||||
generation: update
|
||||
globally-quoted-identifiers: true
|
||||
mailer:
|
||||
from: test@quarkus.io
|
||||
# host: smtp.sendgrid.net
|
||||
host: localhost
|
||||
port: 2525
|
||||
# port: 465
|
||||
# ssl: true
|
||||
|
||||
# username: ""
|
||||
# password: ""
|
||||
mock: false
|
||||
@@ -0,0 +1,294 @@
|
||||
-- phpMyAdmin SQL Dump
|
||||
-- version 5.1.1deb5ubuntu1
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- Hôte : localhost:3306
|
||||
-- Généré le : mar. 22 août 2023 à 10:08
|
||||
-- Version du serveur : 8.0.33-0ubuntu0.22.04.4
|
||||
-- Version de PHP : 8.1.2-1ubuntu2.13
|
||||
|
||||
|
||||
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||
SET AUTOCOMMIT = 0;
|
||||
START TRANSACTION;
|
||||
SET time_zone = "+00:00";
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Base de données : `tlc`
|
||||
--
|
||||
CREATE DATABASE IF NOT EXISTS `tlc` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE `tlc`;
|
||||
|
||||
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8mb4 */;
|
||||
|
||||
--
|
||||
-- Base de données : `tlc`
|
||||
--
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Choice`
|
||||
--
|
||||
|
||||
CREATE TABLE `Choice` (
|
||||
`id` bigint NOT NULL,
|
||||
`endDate` datetime(6) DEFAULT NULL,
|
||||
`startDate` datetime(6) DEFAULT NULL,
|
||||
`pollID` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Choice_SEQ`
|
||||
--
|
||||
|
||||
CREATE TABLE `Choice_SEQ` (
|
||||
`next_val` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `Choice_SEQ`
|
||||
--
|
||||
|
||||
INSERT INTO `Choice_SEQ` (`next_val`) VALUES
|
||||
(1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `choice_user`
|
||||
--
|
||||
|
||||
CREATE TABLE `choice_user` (
|
||||
`choice_id` bigint NOT NULL,
|
||||
`user_id` bigint NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Comment`
|
||||
--
|
||||
|
||||
CREATE TABLE `Comment` (
|
||||
`id` bigint NOT NULL,
|
||||
`auteur` varchar(255) DEFAULT NULL,
|
||||
`content` varchar(255) DEFAULT NULL,
|
||||
`pollID` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Comment_SEQ`
|
||||
--
|
||||
|
||||
CREATE TABLE `Comment_SEQ` (
|
||||
`next_val` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `Comment_SEQ`
|
||||
--
|
||||
|
||||
INSERT INTO `Comment_SEQ` (`next_val`) VALUES
|
||||
(1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `MealPreference`
|
||||
--
|
||||
|
||||
CREATE TABLE `MealPreference` (
|
||||
`id` bigint NOT NULL,
|
||||
`content` varchar(255) DEFAULT NULL,
|
||||
`user_id` bigint DEFAULT NULL,
|
||||
`pollID` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `MealPreference_SEQ`
|
||||
--
|
||||
|
||||
CREATE TABLE `MealPreference_SEQ` (
|
||||
`next_val` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `MealPreference_SEQ`
|
||||
--
|
||||
|
||||
INSERT INTO `MealPreference_SEQ` (`next_val`) VALUES
|
||||
(1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Poll`
|
||||
--
|
||||
|
||||
CREATE TABLE `Poll` (
|
||||
`id` bigint NOT NULL,
|
||||
`clos` bit(1) NOT NULL,
|
||||
`createdAt` datetime(6) DEFAULT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`has_meal` bit(1) NOT NULL,
|
||||
`location` varchar(255) DEFAULT NULL,
|
||||
`padURL` varchar(255) DEFAULT NULL,
|
||||
`slug` varchar(255) DEFAULT NULL,
|
||||
`slugAdmin` varchar(255) DEFAULT NULL,
|
||||
`title` varchar(255) DEFAULT NULL,
|
||||
`tlkURL` varchar(255) DEFAULT NULL,
|
||||
`updatedAt` datetime(6) DEFAULT NULL,
|
||||
`selectedChoice_id` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `Poll_SEQ`
|
||||
--
|
||||
|
||||
CREATE TABLE `Poll_SEQ` (
|
||||
`next_val` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `Poll_SEQ`
|
||||
--
|
||||
|
||||
INSERT INTO `Poll_SEQ` (`next_val`) VALUES
|
||||
(1);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `User`
|
||||
--
|
||||
|
||||
CREATE TABLE `User` (
|
||||
`id` bigint NOT NULL,
|
||||
`icsurl` varchar(255) DEFAULT NULL,
|
||||
`mail` varchar(255) DEFAULT NULL,
|
||||
`username` varchar(255) DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Structure de la table `User_SEQ`
|
||||
--
|
||||
|
||||
CREATE TABLE `User_SEQ` (
|
||||
`next_val` bigint DEFAULT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
--
|
||||
-- Déchargement des données de la table `User_SEQ`
|
||||
--
|
||||
|
||||
INSERT INTO `User_SEQ` (`next_val`) VALUES
|
||||
(1);
|
||||
|
||||
--
|
||||
-- Index pour les tables déchargées
|
||||
--
|
||||
|
||||
--
|
||||
-- Index pour la table `Choice`
|
||||
--
|
||||
ALTER TABLE `Choice`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `FKpptbydus718x0n5w5s1hmtvnp` (`pollID`);
|
||||
|
||||
--
|
||||
-- Index pour la table `choice_user`
|
||||
--
|
||||
ALTER TABLE `choice_user`
|
||||
ADD KEY `FK74lqrm3h9f56d6ijnvjobl0wb` (`user_id`),
|
||||
ADD KEY `FKljka9n83yo9s4qpol3wplp1lw` (`choice_id`);
|
||||
|
||||
--
|
||||
-- Index pour la table `Comment`
|
||||
--
|
||||
ALTER TABLE `Comment`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `FKgw1unu5kgu9s7sdkqaoy0kyyh` (`pollID`);
|
||||
|
||||
--
|
||||
-- Index pour la table `MealPreference`
|
||||
--
|
||||
ALTER TABLE `MealPreference`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `FK61nykkil19yk0on84o44ykk3p` (`user_id`),
|
||||
ADD KEY `FK9pk3lx8mh8478nxj8lvxvaox0` (`pollID`);
|
||||
|
||||
--
|
||||
-- Index pour la table `Poll`
|
||||
--
|
||||
ALTER TABLE `Poll`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD UNIQUE KEY `UK_n779urxmh62kwbspgd6gp8564` (`selectedChoice_id`);
|
||||
|
||||
--
|
||||
-- Index pour la table `User`
|
||||
--
|
||||
ALTER TABLE `User`
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
--
|
||||
-- Contraintes pour les tables déchargées
|
||||
--
|
||||
|
||||
--
|
||||
-- Contraintes pour la table `Choice`
|
||||
--
|
||||
ALTER TABLE `Choice`
|
||||
ADD CONSTRAINT `FKpptbydus718x0n5w5s1hmtvnp` FOREIGN KEY (`pollID`) REFERENCES `Poll` (`id`);
|
||||
|
||||
--
|
||||
-- Contraintes pour la table `choice_user`
|
||||
--
|
||||
ALTER TABLE `choice_user`
|
||||
ADD CONSTRAINT `FK74lqrm3h9f56d6ijnvjobl0wb` FOREIGN KEY (`user_id`) REFERENCES `User` (`id`),
|
||||
ADD CONSTRAINT `FKljka9n83yo9s4qpol3wplp1lw` FOREIGN KEY (`choice_id`) REFERENCES `Choice` (`id`);
|
||||
|
||||
--
|
||||
-- Contraintes pour la table `Comment`
|
||||
--
|
||||
ALTER TABLE `Comment`
|
||||
ADD CONSTRAINT `FKgw1unu5kgu9s7sdkqaoy0kyyh` FOREIGN KEY (`pollID`) REFERENCES `Poll` (`id`);
|
||||
|
||||
--
|
||||
-- Contraintes pour la table `MealPreference`
|
||||
--
|
||||
ALTER TABLE `MealPreference`
|
||||
ADD CONSTRAINT `FK61nykkil19yk0on84o44ykk3p` FOREIGN KEY (`user_id`) REFERENCES `User` (`id`),
|
||||
ADD CONSTRAINT `FK9pk3lx8mh8478nxj8lvxvaox0` FOREIGN KEY (`pollID`) REFERENCES `Poll` (`id`);
|
||||
|
||||
--
|
||||
-- Contraintes pour la table `Poll`
|
||||
--
|
||||
ALTER TABLE `Poll`
|
||||
ADD CONSTRAINT `FKfdictafwo8dwab5rjrjkmmxri` FOREIGN KEY (`selectedChoice_id`) REFERENCES `Choice` (`id`);
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
@@ -0,0 +1,39 @@
|
||||
#
|
||||
# Licensed to Apereo under one or more contributor license
|
||||
# agreements. See the NOTICE file distributed with this work
|
||||
# for additional information regarding copyright ownership.
|
||||
# Apereo licenses this file to you under the Apache License,
|
||||
# Version 2.0 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a
|
||||
# copy of the License at the following location:
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Known optional properties for ical4j:
|
||||
#net.fortuna.ical4j.parser=net.fortuna.ical4j.data.HCalendarParserFactory
|
||||
#net.fortuna.ical4j.timezone.registry=net.fortuna.ical4j.model.DefaultTimeZoneRegistryFactory
|
||||
#net.fortuna.ical4j.timezone.update.enabled={true|false}
|
||||
#net.fortuna.ical4j.timezone.date.floating={true|false}
|
||||
#net.fortuna.ical4j.factory.decoder=net.fortuna.ical4j.util.DefaultDecoderFactory
|
||||
#net.fortuna.ical4j.factory.encoder=net.fortuna.ical4j.util.DefaultEncoderFactory
|
||||
#net.fortuna.ical4j.recur.maxincrementcount=1000
|
||||
#ical4j.unfolding.relaxed={true|false}
|
||||
#ical4j.parsing.relaxed={true|false}
|
||||
#ical4j.validation.relaxed={true|false}
|
||||
#ical4j.compatibility.outlook={true|false}
|
||||
#ical4j.compatibility.notes={true|false}
|
||||
|
||||
# Values...
|
||||
ical4j.unfolding.relaxed=true
|
||||
ical4j.compatibility.outlook=true
|
||||
ical4j.compatibility.notes=true
|
||||
ical4j.parsing.relaxed=true
|
||||
ical4j.validation.relaxed=true
|
||||
@@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name" : "net.fortuna.ical4j.util.JCacheTimeZoneCache",
|
||||
"allDeclaredConstructors" : true,
|
||||
"allPublicConstructors" : true,
|
||||
"allDeclaredMethods" : true,
|
||||
"allPublicMethods" : true,
|
||||
"allDeclaredFields" : true,
|
||||
"allPublicFields" : true
|
||||
},
|
||||
{
|
||||
"name" : "net.fortuna.ical4j.util.MapTimeZoneCache",
|
||||
"allDeclaredConstructors" : true,
|
||||
"allPublicConstructors" : true,
|
||||
"allDeclaredMethods" : true,
|
||||
"allPublicMethods" : true,
|
||||
"allDeclaredFields" : true,
|
||||
"allPublicFields" : true
|
||||
}
|
||||
|
||||
]
|
||||
18
ansible/files/doodlestudent/front/.browserslistrc
Normal file
18
ansible/files/doodlestudent/front/.browserslistrc
Normal file
@@ -0,0 +1,18 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line.
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
||||
16
ansible/files/doodlestudent/front/.editorconfig
Normal file
16
ansible/files/doodlestudent/front/.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
46
ansible/files/doodlestudent/front/.gitignore
vendored
Normal file
46
ansible/files/doodlestudent/front/.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events*.json
|
||||
speed-measure-plugin*.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
27
ansible/files/doodlestudent/front/README.md
Normal file
27
ansible/files/doodlestudent/front/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Tlcfront
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.1.7.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `npm start` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
29
ansible/files/doodlestudent/front/TODO.md
Normal file
29
ansible/files/doodlestudent/front/TODO.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## TODO
|
||||
|
||||
[X] nombre de participants dans la vue edit
|
||||
[X] Vue Admin
|
||||
[X] Selection date retenue
|
||||
[X] Sondage clos dans la vue edit et admin
|
||||
[X] Modification par admin d'un doodle en cours
|
||||
[X] Chargement ics externe dans la vue edit calendrier
|
||||
[X] Import ICS coté serveur et complétion automatique des dispos
|
||||
[X] Test Chargement ics externe dans la vue edit calendrier
|
||||
[X] ICS avec évènement à répétition
|
||||
[X] Date sur plusieurs jours
|
||||
[X] Date journée entière
|
||||
[X] Menu dans la vue admin pour edit, export
|
||||
[X] Réintégration etherpad avec prop dans le fichier de conf
|
||||
[X] Réintégration export Excel
|
||||
[X] Affichage TalTo et Pad si not null
|
||||
[X] Affichage commentaire
|
||||
[X] envoie de mail avec ics
|
||||
[X] Test envoi de mail avec ics à la cloture du poll
|
||||
[X] Figé le poll séléctionné quand poll séléction date poll validé
|
||||
[X] Chargement ICS dans la vue createPoll
|
||||
[ ] Test etherpad client
|
||||
|
||||
[ ] Modification par un utilisateur des données déjà rentrées pour un poll
|
||||
[ ] Gestion du choix peut être (relation suppléméntaire entre choice et user)
|
||||
[ ] DSL pour la sélection automatique des plages libres à la création d'un poll
|
||||
[ ] Vue mensuel
|
||||
[ ] Sécurité SlugAdmin envoyé uniquement
|
||||
131
ansible/files/doodlestudent/front/angular.json
Normal file
131
ansible/files/doodlestudent/front/angular.json
Normal file
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"tlcfront": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/tlcfront",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"aot": true,
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"./node_modules/bootstrap/dist/css/bootstrap.css",
|
||||
"node_modules/primeng/resources/themes/bootstrap4-light-blue/theme.css",
|
||||
"node_modules/primeicons/primeicons.css",
|
||||
"node_modules/primeng/resources/primeng.min.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
"./node_modules/jquery/dist/jquery.js",
|
||||
"./node_modules/bootstrap/dist/js/bootstrap.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "6kb",
|
||||
"maximumError": "10kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "tlcfront:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "tlcfront:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "tlcfront:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.spec.json",
|
||||
"e2e/tsconfig.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "tlcfront:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "tlcfront:serve:production"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}},
|
||||
"defaultProject": "tlcfront"
|
||||
}
|
||||
36
ansible/files/doodlestudent/front/e2e/protractor.conf.js
Normal file
36
ansible/files/doodlestudent/front/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// @ts-check
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
|
||||
|
||||
/**
|
||||
* @type { import("protractor").Config }
|
||||
*/
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
browserName: 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({
|
||||
spec: {
|
||||
displayStacktrace: StacktraceOption.PRETTY
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
23
ansible/files/doodlestudent/front/e2e/src/app.e2e-spec.ts
Normal file
23
ansible/files/doodlestudent/front/e2e/src/app.e2e-spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('tlcfront app is running!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
} as logging.Entry));
|
||||
});
|
||||
});
|
||||
11
ansible/files/doodlestudent/front/e2e/src/app.po.ts
Normal file
11
ansible/files/doodlestudent/front/e2e/src/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl) as Promise<unknown>;
|
||||
}
|
||||
|
||||
getTitleText(): Promise<string> {
|
||||
return element(by.css('app-root .content span')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
||||
14
ansible/files/doodlestudent/front/e2e/tsconfig.json
Normal file
14
ansible/files/doodlestudent/front/e2e/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
32
ansible/files/doodlestudent/front/karma.conf.js
Normal file
32
ansible/files/doodlestudent/front/karma.conf.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/tlcfront'),
|
||||
reports: ['html', 'lcovonly', 'text-summary'],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
||||
13960
ansible/files/doodlestudent/front/package-lock.json
generated
Normal file
13960
ansible/files/doodlestudent/front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
ansible/files/doodlestudent/front/package.json
Normal file
57
ansible/files/doodlestudent/front/package.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "tlcfront",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve --proxy-config proxy.conf.json",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "16.2.1",
|
||||
"@angular/common": "16.2.1",
|
||||
"@angular/compiler": "16.2.1",
|
||||
"@angular/core": "16.2.1",
|
||||
"@angular/forms": "16.2.1",
|
||||
"@angular/localize": "16.2.1",
|
||||
"@angular/platform-browser": "16.2.1",
|
||||
"@angular/platform-browser-dynamic": "16.2.1",
|
||||
"@angular/router": "16.2.1",
|
||||
"@fullcalendar/angular": "6.1.8",
|
||||
"@fullcalendar/core": "6.1.8",
|
||||
"@fullcalendar/daygrid": "6.1.8",
|
||||
"@fullcalendar/interaction": "6.1.8",
|
||||
"@fullcalendar/timegrid": "6.1.8",
|
||||
"@ng-bootstrap/ng-bootstrap": "15.1.1",
|
||||
"bootstrap": "5.3.1",
|
||||
"jquery": "3.7.0",
|
||||
"primeicons": "6.0.1",
|
||||
"primeng": "16.2.0",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.6.2",
|
||||
"zone.js": "0.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "16.2.0",
|
||||
"@angular/cli": "16.2.0",
|
||||
"@angular/compiler-cli": "16.2.1",
|
||||
"@types/jasmine": "4.3.5",
|
||||
"@types/jasminewd2": "2.0.10",
|
||||
"@types/node": "20.5.1",
|
||||
"codelyzer": "6.0.2",
|
||||
"jasmine-core": "5.1.0",
|
||||
"jasmine-spec-reporter": "7.0.0",
|
||||
"karma": "6.4.2",
|
||||
"karma-chrome-launcher": "3.2.0",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-jasmine": "5.1.0",
|
||||
"karma-jasmine-html-reporter": "2.1.0",
|
||||
"protractor": "7.0.0",
|
||||
"ts-node": "10.9.1",
|
||||
"tslint": "6.1.0",
|
||||
"typescript": "5.1.6"
|
||||
}
|
||||
}
|
||||
8
ansible/files/doodlestudent/front/proxy.conf.json
Normal file
8
ansible/files/doodlestudent/front/proxy.conf.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api/*": {
|
||||
"target": "http://localhost:8080",
|
||||
"secure": false,
|
||||
"logLevel": "debug",
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.Poll_Informations {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Meal_Preferences {
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid #F0F4F8;
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Poll_Description_Title {
|
||||
font-size: 0.8rem;
|
||||
color: #243B53;
|
||||
}
|
||||
|
||||
.Poll_Infos {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Poll_Info {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<div class="Container">
|
||||
|
||||
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }" />
|
||||
|
||||
<!-- { isModalOpened &&
|
||||
<div className="modal" onClick={() => setIsModalOpened(false)}>
|
||||
<div className="Export_Modal" >
|
||||
<a className="Export Disabled" target="_blank" rel="noopener noreferrer">
|
||||
<svg aria-hidden="true" width="40px" height="40px" focusable="false" data-prefix="fas" data-icon="file-pdf" className="Export_Icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M181.9 256.1c-5-16-4.9-46.9-2-46.9 8.4 0 7.6 36.9 2 46.9zm-1.7 47.2c-7.7 20.2-17.3 43.3-28.4 62.7 18.3-7 39-17.2 62.9-21.9-12.7-9.6-24.9-23.4-34.5-40.8zM86.1 428.1c0 .8 13.2-5.4 34.9-40.2-6.7 6.3-29.1 24.5-34.9 40.2zM248 160h136v328c0 13.3-10.7 24-24 24H24c-13.3 0-24-10.7-24-24V24C0 10.7 10.7 0 24 0h200v136c0 13.2 10.8 24 24 24zm-8 171.8c-20-12.2-33.3-29-42.7-53.8 4.5-18.5 11.6-46.6 6.2-64.2-4.7-29.4-42.4-26.5-47.8-6.8-5 18.3-.4 44.1 8.1 77-11.6 27.6-28.7 64.6-40.8 85.8-.1 0-.1.1-.2.1-27.1 13.9-73.6 44.5-54.5 68 5.6 6.9 16 10 21.5 10 17.9 0 35.7-18 61.1-61.8 25.8-8.5 54.1-19.1 79-23.2 21.7 11.8 47.1 19.5 64 19.5 29.2 0 31.2-32 19.7-43.4-13.9-13.6-54.3-9.7-73.6-7.2zM377 105L279 7c-4.5-4.5-10.6-7-17-7h-6v128h128v-6.1c0-6.3-2.5-12.4-7-16.9zm-74.1 255.3c4.1-2.7-2.5-11.9-42.8-9 37.1 15.8 42.8 9 42.8 9z"></path></svg>
|
||||
<span>PDF (Premium)</span>
|
||||
</a>
|
||||
<a className="Export" target="_blank" rel="noopener noreferrer" href={`${BASE_URL}/polls/${slug}/results`}>
|
||||
<svg aria-hidden="true" width="40px" height="40px" focusable="false" data-prefix="fas" data-icon="file-excel" className="Export_Icon" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M224 136V0H24C10.7 0 0 10.7 0 24v464c0 13.3 10.7 24 24 24h336c13.3 0 24-10.7 24-24V160H248c-13.2 0-24-10.8-24-24zm60.1 106.5L224 336l60.1 93.5c5.1 8-.6 18.5-10.1 18.5h-34.9c-4.4 0-8.5-2.4-10.6-6.3C208.9 405.5 192 373 192 373c-6.4 14.8-10 20-36.6 68.8-2.1 3.9-6.1 6.3-10.5 6.3H110c-9.5 0-15.2-10.5-10.1-18.5l60.3-93.5-60.3-93.5c-5.2-8 .6-18.5 10.1-18.5h34.8c4.4 0 8.5 2.4 10.6 6.3 26.1 48.8 20 33.6 36.6 68.5 0 0 6.1-11.7 36.6-68.5 2.1-3.9 6.2-6.3 10.6-6.3H274c9.5-.1 15.2 10.4 10.1 18.4zM384 121.9v6.1H256V0h6.1c6.4 0 12.5 2.5 17 7l97.9 98c4.5 4.5 7 10.6 7 16.9z"></path></svg>
|
||||
<span>EXCEL</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}-->
|
||||
|
||||
<app-top-bar [adminSlug]="poll?.slugAdmin" [slug]="poll?.slug" [padURL]="poll?.padURL" [talkToURL]="poll?.tlkURL" ></app-top-bar>
|
||||
|
||||
<p-card>
|
||||
<p-toast></p-toast>
|
||||
<ng-template pTemplate="title">
|
||||
<h1>{{poll?.title}}</h1>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
<div class="Dates"><span>Créé il y a {{poll?.createdAt | dateago}}</span></div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="Poll_Infos">
|
||||
<p class="Poll_Location"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-map-pin">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>{{poll?.location}}</p>
|
||||
<div *ngIf="poll?.has_meal" class="Poll_Has_Meal"><svg class="feather" aria-hidden="true" width="20" height="20"
|
||||
focusable="false" data-prefix="fas" data-icon="utensils" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 416 512">
|
||||
<path fill="currentColor"
|
||||
d="M207.9 15.2c.8 4.7 16.1 94.5 16.1 128.8 0 52.3-27.8 89.6-68.9 104.6L168 486.7c.7 13.7-10.2 25.3-24 25.3H80c-13.7 0-24.7-11.5-24-25.3l12.9-238.1C27.7 233.6 0 196.2 0 144 0 109.6 15.3 19.9 16.1 15.2 19.3-5.1 61.4-5.4 64 16.3v141.2c1.3 3.4 15.1 3.2 16 0 1.4-25.3 7.9-139.2 8-141.8 3.3-20.8 44.7-20.8 47.9 0 .2 2.7 6.6 116.5 8 141.8.9 3.2 14.8 3.4 16 0V16.3c2.6-21.6 44.8-21.4 48-1.1zm119.2 285.7l-15 185.1c-1.2 14 9.9 26 23.9 26h56c13.3 0 24-10.7 24-24V24c0-13.2-10.7-24-24-24-82.5 0-221.4 178.5-64.9 300.9z">
|
||||
</path>
|
||||
</svg>
|
||||
Cet évènement contient un repas
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div >
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive-sm card">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2"></th>
|
||||
<th *ngFor="let ev of events" class="text-light" style="text-align: center;background-color: #545B62">{{ev.start | date:'EEEE d LLLL': 'CEST':'fr'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th *ngFor="let ev of events" style="text-align: center">{{ev.start | date:'H:mm'}} <BR>-<BR> {{ev.end | date:'H:mm'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span>{{uniqueUsers.length}} participant</span><span *ngIf="uniqueUsers.length > 1">s</span></th>
|
||||
<th *ngFor="let pc of poll?.pollChoices" style="text-align: center">{{pc.users.length}}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of userChoices | keyvalue">
|
||||
<td><input type="text" [disabled]='true' pInputText [ngModel]="uniqueUsers | usernamePipe:u.key"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [disabled]='true' [binary]="true" [ngModel]="u.value | selecteddate4userPipe:u.key:ev" ></p-checkbox></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-button [disabled]="poll?.clos" (onClick)="selectEvent($event,ev )" >sélectionner cette date</p-button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
|
||||
<app-show-comments *ngIf="poll" [comments]="comments"></app-show-comments>
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminPollComponent } from './admin-poll.component';
|
||||
|
||||
describe('AdminPollComponent', () => {
|
||||
let component: AdminPollComponent;
|
||||
let fixture: ComponentFixture<AdminPollComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AdminPollComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdminPollComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { Poll, User, PollChoice, PollCommentElement, ChoiceUser } from '../model/model';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-poll',
|
||||
templateUrl: './admin-poll.component.html',
|
||||
styleUrls: ['./admin-poll.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent]
|
||||
|
||||
})
|
||||
export class AdminPollComponent implements OnInit {
|
||||
|
||||
constructor(public messageService: MessageService, private actRoute: ActivatedRoute, private pollService: PollService) { }
|
||||
slugid: string;
|
||||
poll: Poll;
|
||||
events: EventInput[] = [];
|
||||
uniqueUsers: User[] = [];
|
||||
userChoices: Map<number, PollChoice[]> = new Map();
|
||||
comments: PollCommentElement[];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugadminid');
|
||||
this.pollService.getPollBySlugAdminId(this.slugid).subscribe(p => {
|
||||
this.poll = p;
|
||||
if (p != null){
|
||||
this.pollService.getComentsBySlugId(this.poll?.slug).subscribe(cs => this.comments = cs);
|
||||
}
|
||||
this.uniqueUsers.splice(0, this.uniqueUsers.length);
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(user => {
|
||||
if (this.uniqueUsers.filter(us => us.id === user.id).length === 0 ){
|
||||
this.uniqueUsers.push(user);
|
||||
this.userChoices.set(user.id, []);
|
||||
}
|
||||
});
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
backgroundColor: 'red',
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
selected: false
|
||||
},
|
||||
};
|
||||
this.events.push(evt);
|
||||
});
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(us => {
|
||||
this.userChoices.get(us.id).push(pc);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
selectEvent($event: any, event: EventInput): void{
|
||||
this.pollService.selectEvent(event.extendedProps.choiceid).subscribe(e => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Le sondage est maintenant close'}
|
||||
);
|
||||
this.poll.clos = true;
|
||||
}, (error) => {
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Sélection de cette date impossible',
|
||||
detail: 'Le sondage n\'a pu être clos'}
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
:root {
|
||||
--header-height : 180px;
|
||||
--participant-width : 230px;
|
||||
--cell-width : 65px;
|
||||
--cell-height: 40px;
|
||||
--new-participant-height : 65px;
|
||||
--cell-padding : 1rem;
|
||||
|
||||
--color-new-participant : #E6E6FF;
|
||||
--color-vote-yes : #E6E6FF;
|
||||
--color-vote-no :rgb(254,246,246) ;
|
||||
}
|
||||
|
||||
ul{
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.Poll_Vote_Wrapper{
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.Poll_Vote_Content{
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
/*border : 2px solid black; */
|
||||
max-width: 100%;
|
||||
max-height: 390px;
|
||||
overflow-y: scroll;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Cell_Poll_Header{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Cell_Option .Cell_Poll_Header, .Cell_Option_Votes{
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Options{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.Cell_Option{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*Tailles de cellules*/
|
||||
.Cell_Participants_Header,.Cell_Participant_Count,
|
||||
.Cell_New_Participant,.Cell_Participant{
|
||||
width: var(--participant-width);
|
||||
}
|
||||
.Cell_Option_Name,.Cell_Option_Count,
|
||||
.Cell_Option_New_Participant_Vote,.Cell_Option_Vote_Yes,
|
||||
.Cell_Option_Vote_No{
|
||||
width: var(--cell-width);
|
||||
}
|
||||
/*.Cell_New_Participant,.Cell_Option_New_Participant_Vote{
|
||||
height: var(--new-participant-height);
|
||||
}*/
|
||||
|
||||
.Cell_Option_New_Participant_Vote {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.Checkbox_Btn.LastCheck {
|
||||
border-radius: 0 0 5px 0;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Participants_Header,.Cell_Option_Name, .Cell_Header_Name{
|
||||
height: var(--header-height);
|
||||
}
|
||||
/*.Cell_Participant_Count,.Cell_Option_Count,
|
||||
.Cell_Option_Vote_Yes,.Cell_Option_Vote_No,.Cell_Participant{
|
||||
height: var(--cell-height);
|
||||
}*/
|
||||
|
||||
.Cell_Participant_Count {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
/* Disposition dans les cellules */
|
||||
.Cell_Participants_Header,.Cell_Participant_Count,
|
||||
.Cell_Option_Count,
|
||||
.Cell_Option_Vote_Yes,.Cell_Option_Vote_No{
|
||||
padding: var(--cell-padding);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
/*border: 1px solid black;*/
|
||||
}
|
||||
|
||||
.Poll_Vote_Content {
|
||||
border: 1px solid #E6E6FF;
|
||||
}
|
||||
|
||||
/*.Cell_Participant,.Cell_New_Participant{
|
||||
padding: var(--cell-padding);
|
||||
padding-left: 20%;
|
||||
border: 1px solid black;
|
||||
text-align: left;
|
||||
}*/
|
||||
|
||||
.Cell_Option_Name, .Cell_Participants_Header {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Participant, .Cell_Vote {
|
||||
height: 50px;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
||||
/*Couleurs*/
|
||||
.Cell_Option_New_Participant_Vote{
|
||||
background-color: var(--color-new-participant);
|
||||
}
|
||||
.Cell_Option_Vote_Yes{
|
||||
background-color: var(--color-vote-yes)
|
||||
}
|
||||
.Cell_Option_Vote_No{
|
||||
background-color: var(--color-vote-no)
|
||||
}
|
||||
/* Fixe la premiere colonne et parametrage du scroll*/
|
||||
|
||||
.Cell_Options{
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
|
||||
.Cell_New_Participant_Input {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.Cell_New_Participant_Input.error {
|
||||
border: 2px solid #EF4E4E;
|
||||
}
|
||||
|
||||
.Poll_View_Btn {
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
border: 1px solid #1D0EBE;
|
||||
display: flex;
|
||||
color: #4D3DF7;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.Poll_View_Btn:first-child {
|
||||
border-radius: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.Poll_View_Btn:last-child {
|
||||
border-radius: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
.Poll_View_Btn.active {
|
||||
background-color: #4D3DF7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Poll_Btns {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.Meal_Preferences_Toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Poll_Location {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.feather {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.Cell_Header {
|
||||
width: var(--participant-width);
|
||||
}
|
||||
|
||||
.Poll_Has_Meal {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Poll_Description_Title {
|
||||
font-size: 0.8rem;
|
||||
color: #243B53;
|
||||
}
|
||||
|
||||
.Poll_Infos {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Poll_Info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.green {
|
||||
background-color: #199473;
|
||||
}
|
||||
|
||||
.green:hover {
|
||||
background-color: #147D64;
|
||||
}
|
||||
|
||||
.Poll_Vote_Action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.Cell_Poll_Header.Cell_Option_Name {
|
||||
width: 100%!important;
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: #65D6AD;
|
||||
color: #014D40;
|
||||
}
|
||||
|
||||
.Poll_Subtitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
font-size: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.Dates {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 0.8rem;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.Pad_Url {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Edit_Link {
|
||||
margin-left: 1rem;
|
||||
font-weight: 500;
|
||||
color: #4d3cf7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Edit_Link:hover {
|
||||
color: #1D0EBE;
|
||||
}
|
||||
|
||||
.Link {
|
||||
text-decoration: none;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.Cell_Day {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Poll_Start_Date {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.Poll_Date {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Checkbox_Btn {
|
||||
width: 65px;
|
||||
height: 50px;
|
||||
border: none;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
transition: background-color 0.3s linear;
|
||||
border: 1px solid #4d3cf7;
|
||||
}
|
||||
|
||||
.Checkbox_Btn.Active {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Cell_New_Participant {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
|
||||
.Links {
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
background-color: #4D3DF7;
|
||||
border-radius: 5px 5px 0 0;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
|
||||
}
|
||||
|
||||
.Links_Right {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Links_Left {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Feat_Link {
|
||||
padding: 0.7rem 1rem;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Feat_Link:last-child {
|
||||
border-radius: 0 5px 0 0;
|
||||
}
|
||||
|
||||
.Feat_Link.Unique {
|
||||
border-radius: 5px 5px 0 0!important;
|
||||
}
|
||||
|
||||
.Feat_Link:hover {
|
||||
background-color: #0C008C;
|
||||
}
|
||||
|
||||
.MealPref {
|
||||
padding: 1rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.Author_MealPref {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
.Author_Comment {
|
||||
font-weight: bold;
|
||||
color: #4d3cf7;
|
||||
}
|
||||
|
||||
|
||||
.orange {
|
||||
background-color: #F7D070;
|
||||
}
|
||||
|
||||
.orange:hover {
|
||||
background-color: #E9B949;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
.Export_Modal {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto; /* 15% from the top and centered */
|
||||
width: 500px; /* Could be more or less, depending on screen size */
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 160px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
|
||||
.Export {
|
||||
padding: 20px;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #4d3cf7;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
transition: background-color 0.3s linear;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.Export:hover {
|
||||
background-color: #4d3cf7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Export:first-child {
|
||||
border-right: 4px solid #4d3cf7;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.Export:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.Export_Icon {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.Poll_Informations {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.Meal_Preferences {
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid #F0F4F8;
|
||||
}
|
||||
|
||||
.Disabled {
|
||||
background-color: #cccccc;
|
||||
}
|
||||
|
||||
.Disabled:hover {
|
||||
color: #4d3cf7;
|
||||
background-color: #cccccc;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.Comment {
|
||||
padding: 1rem;
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<div class="Container">
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }" />
|
||||
<app-top-bar [slug]="poll?.slug" [padURL]="poll?.padURL" [talkToURL]="poll?.tlkURL" ></app-top-bar>
|
||||
|
||||
|
||||
<p-card>
|
||||
<p-toast></p-toast>
|
||||
<ng-template pTemplate="title">
|
||||
<h1>{{poll?.title}}</h1>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
<div class="Dates"><span>Créé il y a {{poll?.createdAt | dateago}}</span></div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="Poll_Infos">
|
||||
<p class="Poll_Location"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
|
||||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="feather feather-map-pin">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>{{poll?.location}}</p>
|
||||
<div *ngIf="poll?.has_meal" class="Poll_Has_Meal"><svg class="feather" aria-hidden="true" width="20" height="20"
|
||||
focusable="false" data-prefix="fas" data-icon="utensils" role="img" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 416 512">
|
||||
<path fill="currentColor"
|
||||
d="M207.9 15.2c.8 4.7 16.1 94.5 16.1 128.8 0 52.3-27.8 89.6-68.9 104.6L168 486.7c.7 13.7-10.2 25.3-24 25.3H80c-13.7 0-24.7-11.5-24-25.3l12.9-238.1C27.7 233.6 0 196.2 0 144 0 109.6 15.3 19.9 16.1 15.2 19.3-5.1 61.4-5.4 64 16.3v141.2c1.3 3.4 15.1 3.2 16 0 1.4-25.3 7.9-139.2 8-141.8 3.3-20.8 44.7-20.8 47.9 0 .2 2.7 6.6 116.5 8 141.8.9 3.2 14.8 3.4 16 0V16.3c2.6-21.6 44.8-21.4 48-1.1zm119.2 285.7l-15 185.1c-1.2 14 9.9 26 23.9 26h56c13.3 0 24-10.7 24-24V24c0-13.2-10.7-24-24-24-82.5 0-221.4 178.5-64.9 300.9z">
|
||||
</path>
|
||||
</svg>
|
||||
Cet évènement contient un repas
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="nom">Nom prénom participant</label>
|
||||
<input #nom="ngModel" id="nom" type="text" required pInputText [(ngModel)]="personalInformation.nom"
|
||||
[ngClass]="{'p-invalid': (nom.invalid && submitted) || (nom.dirty && nom.invalid)}">
|
||||
<small *ngIf="(nom.invalid && submitted) || (nom.dirty && nom.invalid)" class="p-error">Le nom est
|
||||
requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="mail">Email participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loademail" class="pi pi-spin pi-spinner" ></i>
|
||||
|
||||
<input #mail="ngModel" id="mail" type="email" required pInputText (change)="getUserFromMail()" [(ngModel)]="personalInformation.mail"
|
||||
[ngClass]="{'p-invalid': (mail.invalid && submitted) || (mail.dirty && mail.invalid)}">
|
||||
</span>
|
||||
<small class="p-error" *ngIf="(mail.invalid && submitted )|| (mail.dirty && mail.invalid)">Le
|
||||
mail est requis.</small>
|
||||
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous un agenda avec un flux ics accessible ?</p>
|
||||
<p-inputSwitch [(ngModel)]="hasics"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="hasics" class="p-field">
|
||||
<label for="ics">URL ICS du participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loadics" class="pi pi-spin pi-spinner" ></i>
|
||||
<input #mail="ngModel" id="ics" type="text" pInputText (change)="getICS()" [(ngModel)]="personalInformation.ics">
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="poll?.has_meal" class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous des préférences alimentaires ?</p>
|
||||
<p-inputSwitch [(ngModel)]="personalInformation.pref"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="personalInformation?.pref" class="p-field">
|
||||
<label for="desc">Description préférences alimentaires</label>
|
||||
<textarea #desc="ngModel" id="desc" required pInputTextarea [(ngModel)]="personalInformation.desc"
|
||||
[ngClass]="{'p-invalid': (desc.invalid && submitted) || (desc.dirty && desc.invalid)}"></textarea>
|
||||
<small class="p-error" *ngIf="(desc.invalid && submitted) || (desc.dirty && desc.invalid)">La description est
|
||||
requise.</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p-selectButton [options]="calendarortableoption" [(ngModel)]="calendarortable">
|
||||
<ng-template let-item>
|
||||
<i [class]="item.icon">Vue {{item.text}}</i>
|
||||
</ng-template>
|
||||
</p-selectButton>
|
||||
</div>
|
||||
|
||||
<div *ngIf="calendarortable ==='calendar'">
|
||||
<full-calendar #calendar [options]="options"></full-calendar>
|
||||
</div>
|
||||
|
||||
<div *ngIf="calendarortable !='calendar'">
|
||||
<div class="container-fluid">
|
||||
<div class="table-responsive-sm card">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowspan="2"></th>
|
||||
<th *ngFor="let ev of events" class="text-light" style="text-align: center;background-color: #545B62">{{ev.start | date:'EEEE d LLLL': 'CEST':'fr'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th *ngFor="let ev of events" style="text-align: center">{{ev.start | date:'H:mm'}} <BR>-<BR> {{ev.end | date:'H:mm'}}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><span>{{uniqueUsers.length + 1}} participant</span><span *ngIf="uniqueUsers.length > 0">s</span></th>
|
||||
<th *ngFor="let pc of poll?.pollChoices" style="text-align: center">{{pc.users.length}}</th>
|
||||
</tr>
|
||||
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of userChoices | keyvalue">
|
||||
<td><input type="text" [disabled]='true' pInputText [ngModel]="uniqueUsers | usernamePipe:u.key"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [disabled]='true' [binary]="true" [ngModel]="u.value | selecteddate4userPipe:u.key:ev" ></p-checkbox></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td><input #nomvotant="" id="nomvotant" type="text" required pInputText [(ngModel)]="personalInformation.nom"></td>
|
||||
<td *ngFor="let ev of events" style="text-align: center"><p-checkbox [binary]="true" (onChange)="updateEvent($event,ev )" [ngModel]="ev.extendedProps.selected" ></p-checkbox></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p-button [disabled]="voeuxsoumis" label="Soumettre voeux" (onClick)="createReponse()"
|
||||
icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<app-show-comments *ngIf="poll" [comments]="comments"></app-show-comments>
|
||||
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="comment">Auteur du commentaire associé à ce sondage</label>
|
||||
<input #comment="ngModel" id="comment" type="text" required pInputText [(ngModel)]="comment1"
|
||||
[ngClass]="{'p-invalid': (comment.invalid && csubmitted) || (comment.dirty && comment.invalid)}">
|
||||
<small *ngIf="(comment.invalid && csubmitted) || (comment.dirty && comment.invalid)" class="p-error">L'auteur
|
||||
du commentaire est requis.</small>
|
||||
</div>
|
||||
|
||||
<div class="p-field">
|
||||
<label for="commentdesc">Commentaire</label>
|
||||
<textarea #commentdesc="ngModel" id="commentdesc" required pInputTextarea [(ngModel)]="commentdesc1"
|
||||
[ngClass]="{'p-invalid': (commentdesc.invalid && csubmitted) || (commentdesc.dirty && commentdesc.invalid)}"></textarea>
|
||||
<small class="p-error"
|
||||
*ngIf="(commentdesc.invalid && csubmitted) || (commentdesc.dirty && commentdesc.invalid)">Le commentaire est
|
||||
requis.</small>
|
||||
</div>
|
||||
<div>
|
||||
<p-button [disabled]="commentsoumis" label="Ajouter commentaire" (onClick)="createComment()"
|
||||
icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<p-button (onClick)="testEvent()">test</p-button>-->
|
||||
<!-- Modal -->
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AnswerPollComponent } from './answer-poll.component';
|
||||
|
||||
describe('AnswerPollComponent', () => {
|
||||
let component: AnswerPollComponent;
|
||||
let fixture: ComponentFixture<AnswerPollComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AnswerPollComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AnswerPollComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,360 @@
|
||||
import { Component, OnInit, ViewChild, AfterViewChecked } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { Poll, ChoiceUser, PollCommentElement, User, PollChoice } from '../model/model';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ModalPollClosComponent } from '../modal-poll-clos/modal-poll-clos.component';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
@Component({
|
||||
selector: 'app-answer-poll',
|
||||
templateUrl: './answer-poll.component.html',
|
||||
styleUrls: ['./answer-poll.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent, NgbModal]
|
||||
|
||||
})
|
||||
export class AnswerPollComponent implements OnInit {
|
||||
|
||||
constructor(public messageService: MessageService,
|
||||
// tslint:disable-next-line:align
|
||||
private actRoute: ActivatedRoute, private pollService: PollService,
|
||||
// tslint:disable-next-line:align
|
||||
private modalService: NgbModal) { }
|
||||
slugid: string;
|
||||
poll: Poll;
|
||||
calendarortableoption: any[];
|
||||
calendarortable = 'calendar';
|
||||
personalInformation: any = {
|
||||
nom: '',
|
||||
mail: '',
|
||||
desc: '',
|
||||
ics: '',
|
||||
pref: false
|
||||
};
|
||||
hasics: false;
|
||||
options: CalendarOptions;
|
||||
@ViewChild('calendar') calendarComponent: FullCalendarComponent;
|
||||
submitted = false;
|
||||
csubmitted = false;
|
||||
voeuxsoumis = false;
|
||||
commentsoumis = false;
|
||||
events: EventInput[] = [];
|
||||
eventsfromics: EventInput[] = [];
|
||||
allevents: EventInput[] = [];
|
||||
loadics = false;
|
||||
loademail = false;
|
||||
comments: PollCommentElement[];
|
||||
|
||||
comment1 = '';
|
||||
commentdesc1 = '';
|
||||
uniqueUsers: User[] = [];
|
||||
userChoices: Map<number, PollChoice[]> = new Map();
|
||||
ngOnInit(): void {
|
||||
this.calendarortableoption = [
|
||||
{ icon: 'pi pi-calendar', text: 'Calendrier', value: 'calendar' },
|
||||
{ icon: 'pi pi-table', text: 'Tableau', value: 'table' },
|
||||
];
|
||||
|
||||
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugid');
|
||||
this.pollService.getPollBySlugId(this.slugid).subscribe(p => {
|
||||
this.poll = p;
|
||||
this.pollService.getComentsBySlugId(this.slugid).subscribe(cs => this.comments = cs);
|
||||
|
||||
|
||||
if (this.poll.clos) {
|
||||
this.openModal();
|
||||
}
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
// calendarApi.next();
|
||||
this.uniqueUsers.splice(0, this.uniqueUsers.length);
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(user => {
|
||||
if (this.uniqueUsers.filter(us => us.id === user.id).length === 0) {
|
||||
this.uniqueUsers.push(user);
|
||||
this.userChoices.set(user.id, []);
|
||||
}
|
||||
});
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
backgroundColor: 'red',
|
||||
id: this.getUniqueId(8),
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
selected: false,
|
||||
},
|
||||
};
|
||||
calendarApi.addEvent(evt, true);
|
||||
this.events.push(evt);
|
||||
this.allevents.push(evt);
|
||||
|
||||
});
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
pc.users.forEach(us => {
|
||||
this.userChoices.get(us.id).push(pc);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
this.options = {
|
||||
initialView: 'timeGridWeek',
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
|
||||
// dateClick: this.handleDateClick.bind(this), // bind is important!
|
||||
/*eventDragStart: (timeSheetEntry, jsEvent, ui, activeView) => {
|
||||
this.eventDragStart(
|
||||
timeSheetEntry, jsEvent, ui, activeView
|
||||
);
|
||||
},
|
||||
eventDragStop: (timeSheetEntry, jsEvent, ui, activeView) => {
|
||||
this.eventDragStop(
|
||||
timeSheetEntry, jsEvent, ui, activeView
|
||||
);
|
||||
},*/
|
||||
// events: this.events,
|
||||
events: this.allevents,
|
||||
editable: false,
|
||||
droppable: false,
|
||||
// selectMirror: true,
|
||||
eventResizableFromStart: false,
|
||||
selectable: false,
|
||||
locale: frLocale,
|
||||
themeSystem: 'bootstrap',
|
||||
slotMinTime: '08:00:00',
|
||||
slotMaxTime: '20:00:00',
|
||||
eventMouseEnter: (mouseEnterInfo) => {
|
||||
|
||||
},
|
||||
eventClick: (info) => {
|
||||
if (!info.event.extendedProps.fromics) {
|
||||
if (info.event.extendedProps.selected) {
|
||||
info.event.setExtendedProp('selected', false);
|
||||
const evt = this.events.filter(e => e.extendedProps.choiceid === info.event.extendedProps.choiceid).pop();
|
||||
evt.extendedProps.selected = false;
|
||||
evt.backgroundColor = 'red';
|
||||
info.event.setProp('backgroundColor', 'red');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt.extendedProps.choiceid)[0].users.splice(-1, 1);
|
||||
|
||||
} else {
|
||||
info.event.setExtendedProp('selected', true);
|
||||
const evt = this.events.filter(e => e.extendedProps.choiceid === info.event.extendedProps.choiceid).pop();
|
||||
evt.extendedProps.selected = true;
|
||||
evt.backgroundColor = 'green';
|
||||
info.event.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// info.event.remove();
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
updateEvent($event: any, event: EventInput): void {
|
||||
|
||||
event.extendedProps.selected = $event.checked;
|
||||
if ($event.checked) {
|
||||
event.backgroundColor = 'green';
|
||||
this.poll.pollChoices.filter(pc => pc.id === event.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
|
||||
} else {
|
||||
event.backgroundColor = 'red';
|
||||
this.poll.pollChoices.filter(pc => pc.id === event.extendedProps.choiceid)[0].users.splice(-1, 1);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
createComment(): void {
|
||||
|
||||
|
||||
if (this.comment1 && this.commentdesc1) {
|
||||
const c: PollCommentElement = {
|
||||
content: this.commentdesc1,
|
||||
auteur: this.comment1
|
||||
};
|
||||
this.pollService.addComment4Poll(this.slugid, c).subscribe(e => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Merci pour ce commentaire'
|
||||
}
|
||||
);
|
||||
this.pollService.getComentsBySlugId(this.poll?.slug).subscribe(cs => this.comments = cs);
|
||||
this.commentsoumis = true;
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis'
|
||||
}
|
||||
);
|
||||
this.csubmitted = true;
|
||||
}
|
||||
|
||||
createReponse(): void {
|
||||
if (this.personalInformation.nom && this.personalInformation.mail &&
|
||||
this.events.filter(e => e.extendedProps.selected).length > 0 &&
|
||||
(this.personalInformation.desc || !this.personalInformation.pref)) {
|
||||
const cu: ChoiceUser = {
|
||||
username: this.personalInformation.nom,
|
||||
mail: this.personalInformation.mail,
|
||||
pref: this.personalInformation.desc,
|
||||
ics: this.personalInformation.ics,
|
||||
choices: this.events.filter(e => e.extendedProps.selected).map(x => x.extendedProps.choiceid)
|
||||
};
|
||||
this.pollService.updateChoice4user(cu).subscribe(e => {
|
||||
// cu.choices.forEach(c => this.poll.pollChoices.filter( c1 => c1.id === c)[0].users.push(e));
|
||||
// if (this.uniqueUsers.filter(u1 => u1.id === e.id ).length === 0) {
|
||||
// this.uniqueUsers.push(e);
|
||||
// }
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Données enregistrées',
|
||||
detail: 'Merci pour votre participation'
|
||||
}
|
||||
);
|
||||
this.voeuxsoumis = true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis et sélectioner au moins une date'
|
||||
}
|
||||
);
|
||||
this.submitted = true;
|
||||
|
||||
}
|
||||
|
||||
getICS(): void {
|
||||
this.loadics = true;
|
||||
this.pollService.getICS(this.slugid, this.personalInformation.ics).subscribe(res => {
|
||||
this.loadics = false;
|
||||
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
if (res.eventdtos.length > 0) {
|
||||
this.eventsfromics.forEach(eid => {
|
||||
const index = this.allevents.indexOf(eid);
|
||||
if (index > -1) {
|
||||
this.allevents.splice(index, 1);
|
||||
}
|
||||
calendarApi.getEventById(eid.id)?.remove();
|
||||
});
|
||||
this.eventsfromics = [];
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
res.eventdtos.forEach(evtdto => { // calendarApi.next();
|
||||
const evt1 =
|
||||
{
|
||||
title: evtdto.description,
|
||||
start: evtdto.startDate,
|
||||
end: evtdto.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
backgroundColor: 'blue',
|
||||
extendedProps: {
|
||||
fromics: true
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
const eventAPI = calendarApi.addEvent(evt1, true);
|
||||
this.eventsfromics.push(evt1);
|
||||
this.allevents.push(evt1);
|
||||
|
||||
});
|
||||
|
||||
const unselected = this.events.map(ev => ev.extendedProps.choiceid);
|
||||
res.selectedChoices.forEach(e => {
|
||||
const index = unselected.indexOf(e);
|
||||
if (index > -1) {
|
||||
unselected.splice(index, 1);
|
||||
}
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'red';
|
||||
evt1.extendedProps.selected = false;
|
||||
evt2.setProp('backgroundColor', 'red');
|
||||
// this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
unselected.forEach(e => {
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'green';
|
||||
evt1.extendedProps.selected = true;
|
||||
evt2.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
}, (err) => {
|
||||
this.loadics = false;
|
||||
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Ne peut récupérer l\'agenda à partir de l\'adresse de l\'ics',
|
||||
detail: 'Une erreur s\'est produite au moment de la récupération de l\'agenda'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
openModal(): void {
|
||||
const modalRef = this.modalService.open(ModalPollClosComponent, {
|
||||
beforeDismiss: () => false,
|
||||
centered: true,
|
||||
windowClass: 'lgModal',
|
||||
backdrop: 'static'
|
||||
});
|
||||
modalRef.componentInstance.poll = this.poll;
|
||||
}
|
||||
|
||||
getUserFromMail(): void {
|
||||
}
|
||||
|
||||
private getUniqueId(parts: number): string {
|
||||
const stringArr = [];
|
||||
for (let i = 0; i < parts; i++) {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const S4 = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
stringArr.push(S4);
|
||||
}
|
||||
return stringArr.join('-');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
import { AnswerPollComponent } from './answer-poll/answer-poll.component';
|
||||
import { AdminPollComponent } from './admin-poll/admin-poll.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: HomeComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: CreatePollComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'update/:slugadminid',
|
||||
component: CreatePollComponentComponent
|
||||
},
|
||||
{
|
||||
path: 'answer/:slugid',
|
||||
component: AnswerPollComponent
|
||||
},
|
||||
{
|
||||
path: 'admin/:slugadminid',
|
||||
component: AdminPollComponent
|
||||
}
|
||||
|
||||
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
87
ansible/files/doodlestudent/front/src/app/app.component.css
Normal file
87
ansible/files/doodlestudent/front/src/app/app.component.css
Normal file
@@ -0,0 +1,87 @@
|
||||
.Container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.SmallCard_Container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.SmallCard {
|
||||
background-color: white;
|
||||
margin-right: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.SmallCard_Image {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.SmallCard_Title {
|
||||
padding: 1rem;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.SmallCard_Subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.SmallCard:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.Home_Container {
|
||||
width: 1024px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Home_Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Home_Logo {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Home_Button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Home_CreateLink {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
background-color: #43dbac;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<router-outlet></router-outlet>
|
||||
@@ -0,0 +1,35 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'tlcfront'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('tlcfront');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('tlcfront app is running!');
|
||||
});
|
||||
});
|
||||
12
ansible/files/doodlestudent/front/src/app/app.component.ts
Normal file
12
ansible/files/doodlestudent/front/src/app/app.component.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css'],
|
||||
providers: [HomeComponentComponent, CreatePollComponentComponent]
|
||||
})
|
||||
export class AppComponent {
|
||||
}
|
||||
86
ansible/files/doodlestudent/front/src/app/app.module.ts
Normal file
86
ansible/files/doodlestudent/front/src/app/app.module.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { CardSmallComponentComponent } from './card-small-component/card-small-component.component';
|
||||
import { HomeComponentComponent } from './home-component/home-component.component';
|
||||
import { CreatePollComponentComponent } from './create-poll-component/create-poll-component.component';
|
||||
import {StepsModule} from 'primeng/steps';
|
||||
import {MenuItem} from 'primeng/api';
|
||||
// import {FullCalendarModule} from 'primeng/fullcalendar';
|
||||
import {ToastModule} from 'primeng/toast';
|
||||
import {MessagesModule} from 'primeng/messages';
|
||||
import {MessageModule} from 'primeng/message';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {InputTextareaModule} from 'primeng/inputtextarea';
|
||||
import {InputSwitchModule} from 'primeng/inputswitch';
|
||||
import {CardModule} from 'primeng/card';
|
||||
import {ButtonModule} from 'primeng/button';
|
||||
import {InputTextModule} from 'primeng/inputtext';
|
||||
import {SelectButtonModule} from 'primeng/selectbutton';
|
||||
import {MenubarModule} from 'primeng/menubar';
|
||||
import {CheckboxModule} from 'primeng/checkbox';
|
||||
|
||||
import dayGridPlugin from '@fullcalendar/daygrid'; // a plugin
|
||||
import interactionPlugin from '@fullcalendar/interaction'; // a plugin
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
import { FullCalendarModule } from '@fullcalendar/angular';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { AnswerPollComponent } from './answer-poll/answer-poll.component';
|
||||
import { AdminPollComponent } from './admin-poll/admin-poll.component';
|
||||
import { DateagoPipe } from './dateago.pipe'; // the main connector. must go first
|
||||
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import localeFr from '@angular/common/locales/fr';
|
||||
import { UsernamePipePipe } from './username-pipe.pipe';
|
||||
import { Selecteddate4userPipePipe } from './selecteddate4user-pipe.pipe';
|
||||
import { ModalPollClosComponent } from './modal-poll-clos/modal-poll-clos.component';
|
||||
import { TopBarComponent } from './top-bar/top-bar.component';
|
||||
import { ShowCommentsComponent } from './show-comments/show-comments.component';
|
||||
|
||||
registerLocaleData(localeFr, 'fr');
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
CardSmallComponentComponent,
|
||||
HomeComponentComponent,
|
||||
CreatePollComponentComponent,
|
||||
AnswerPollComponent,
|
||||
AdminPollComponent,
|
||||
DateagoPipe,
|
||||
UsernamePipePipe,
|
||||
Selecteddate4userPipePipe,
|
||||
ModalPollClosComponent,
|
||||
TopBarComponent,
|
||||
ShowCommentsComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
BrowserAnimationsModule,
|
||||
AppRoutingModule,
|
||||
StepsModule,
|
||||
FullCalendarModule,
|
||||
ToastModule,
|
||||
MessagesModule,
|
||||
MessageModule,
|
||||
InputSwitchModule,
|
||||
CardModule,
|
||||
ButtonModule,
|
||||
InputTextModule,
|
||||
InputTextareaModule,
|
||||
SelectButtonModule,
|
||||
MenubarModule,
|
||||
CheckboxModule,
|
||||
NgbModule
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent],
|
||||
|
||||
|
||||
})
|
||||
export class AppModule { }
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
.SmallCard_Container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.SmallCard {
|
||||
background-color: white;
|
||||
margin-right: 1rem;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.SmallCard_Image {
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.SmallCard_Title {
|
||||
padding: 1rem;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.SmallCard_Subtitle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.SmallCard:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="SmallCard_Container">
|
||||
<div *ngFor="let card of cards" class="SmallCard" [ngStyle]="card.style">
|
||||
<div class="SmallCard_Image">
|
||||
<img [src]="card.image" height="200px"/>
|
||||
</div>
|
||||
<div class="SmallCard_Title">
|
||||
{{ card.title }}
|
||||
</div>
|
||||
<div class="SmallCard_Subtitle">
|
||||
{{ card.subtitle }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CardSmallComponentComponent } from './card-small-component.component';
|
||||
|
||||
describe('CardSmallComponentComponent', () => {
|
||||
let component: CardSmallComponentComponent;
|
||||
let fixture: ComponentFixture<CardSmallComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CardSmallComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CardSmallComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Card } from '../home-component/Card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-card-small-component',
|
||||
templateUrl: './card-small-component.component.html',
|
||||
styleUrls: ['./card-small-component.component.css']
|
||||
})
|
||||
export class CardSmallComponentComponent implements OnInit {
|
||||
|
||||
|
||||
@Input()
|
||||
cards: Card[];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
.CreatePoll_Form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.CreatePoll_Input {
|
||||
width: 100%;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
|
||||
.CreatePoll_Input:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
font-weight: 400;
|
||||
outline: none;
|
||||
color: #102A43;
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
border-radius: .25rem;
|
||||
background-color: #F0F4F8;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
border-color: #4D3DF7;
|
||||
color: #102A43;
|
||||
}
|
||||
|
||||
.CreatePoll_Input input, textarea {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.CreatePoll_Input textarea {
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
.CreatePoll_Buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.CreatePoll_Button {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.CreatePoll_LabelError {
|
||||
color: #EF4E4E;
|
||||
}
|
||||
|
||||
.rbc-calendar {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.rbc-event {
|
||||
background-color: #4D3DF7;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.rbc-today {
|
||||
background-color: #E6E6FF;
|
||||
}
|
||||
|
||||
.rbc-current-time-indicator {
|
||||
background-color: #4D3DF7;
|
||||
}
|
||||
|
||||
.rbc-slot-selection {
|
||||
background-color: #C4C6FF;
|
||||
}
|
||||
|
||||
.rbc-time-view {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.switch {
|
||||
display: inline-block;
|
||||
height: 34px;
|
||||
position: relative;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.slider {
|
||||
background-color: #ccc;
|
||||
bottom: 0;
|
||||
cursor: pointer;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
background-color: #fff;
|
||||
bottom: 4px;
|
||||
content: "";
|
||||
height: 26px;
|
||||
left: 4px;
|
||||
position: absolute;
|
||||
transition: .4s;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #4D3DF7;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.CreatePoll_Switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.CreatePoll_Switch span {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.Poll_Link {
|
||||
font-size: 1.2rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.Recap_Link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.Copy_Link {
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.text-green {
|
||||
color: #199473;
|
||||
}
|
||||
|
||||
.Recap_Links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<div class="Container">
|
||||
<img src="../../assets/flat_logo.png" alt="Logo Simba" height="50px" [ngStyle]="{ 'marginBottom': '1rem' }"/>
|
||||
|
||||
<div class="card">
|
||||
<p-toast></p-toast>
|
||||
<p-steps [model]="items" [readonly]="false" [(activeIndex)]="step"></p-steps>
|
||||
</div>
|
||||
<div [hidden]="step!=0" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="title">
|
||||
Informations
|
||||
</ng-template>
|
||||
<ng-template pTemplate="subtitle">
|
||||
Entrez les informations sur le rendez-vous à planifier
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="p-fluid">
|
||||
<div class="p-field">
|
||||
<label for="titre">Titre de la réunion</label>
|
||||
<input #titre="ngModel" id="titre" type="text" required pInputText
|
||||
[(ngModel)]="poll.title"
|
||||
[ngClass]="{'p-invalid': (titre.invalid && submitted) || (titre.dirty && titre.invalid)}">
|
||||
<small *ngIf="(titre.invalid && submitted) || (titre.dirty && titre.invalid)"
|
||||
class="p-error">Le titre est requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="lieu">Lieu de la réunion</label>
|
||||
<input #lieu="ngModel" id="lieu" type="text" required pInputText
|
||||
[(ngModel)]="poll.location"
|
||||
[ngClass]="{'p-invalid': (lieu.invalid && submitted) || (lieu.dirty && lieu.invalid)}">
|
||||
<small class="p-error" *ngIf="(lieu.invalid && submitted )|| (lieu.dirty && lieu.invalid)">Le
|
||||
lieu est requis.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<label for="desc">Description</label>
|
||||
<textarea #desc="ngModel" id="desc" required pInputTextarea
|
||||
[(ngModel)]="poll.description"
|
||||
[ngClass]="{'p-invalid': (desc.invalid && submitted) || (desc.dirty && desc.invalid)}"></textarea>
|
||||
<small class="p-error" *ngIf="(desc.invalid && submitted) || (desc.dirty && desc.invalid)">La description est requise.</small>
|
||||
</div>
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Repas</p>
|
||||
<p-inputSwitch [ariaLabelledBy]="'repas'" #repas="ngModel" id="repas" [(ngModel)]="poll.has_meal"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="poll?.id" class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Sondage clos</p>
|
||||
<p-inputSwitch #clos="ngModel" id="clos" [(ngModel)]="poll.clos"></p-inputSwitch>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div class="p-grid p-nogutter p-justify-end">
|
||||
<p-button [disabled]=true label="Back" icon="pi pi-angle-left"></p-button>
|
||||
|
||||
<p-button class="float-right" label="Next" (onClick)="nextPage()" icon="pi pi-angle-right" iconPos="right"></p-button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
<div *ngIf="step==1" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="content">
|
||||
<div class="p-fluid">
|
||||
|
||||
<div class="p-field">
|
||||
<p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 1rem;font-weight: normal;">Avez vous un agenda avec un flux ics accessible ?</p>
|
||||
<p-inputSwitch [(ngModel)]="hasics"></p-inputSwitch>
|
||||
</div>
|
||||
<div *ngIf="hasics" class="p-field">
|
||||
<label for="ics">URL ICS du participant</label>
|
||||
<span class="p-input-icon-right">
|
||||
<i *ngIf="loadics" class="pi pi-spin pi-spinner" ></i>
|
||||
<input #mail="ngModel" id="ics" type="text" pInputText (change)="getICS()" [(ngModel)]="ics">
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<full-calendar #calendar [options]="options"></full-calendar>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div>
|
||||
<p-button label="Back" (onClick)="prevPage1()" icon="pi pi-angle-left"></p-button>
|
||||
<p-button class="float-right" label="Next" (onClick)="nextPage1()" icon="pi pi-angle-right" iconPos="right"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
<div *ngIf="step==2" class="stepsdemo-content">
|
||||
<p-card>
|
||||
<ng-template pTemplate="content">
|
||||
Le sondage est créé. <BR>
|
||||
Le lien pour participer est <a [href]="urlsondage" target="_blank">{{urlsondage}} </a>. <BR>
|
||||
Le lien d'administration est <a [href]="urlsondageadmin" target="_blank">{{urlsondageadmin}}</a>.<BR>
|
||||
Un salon a été créé de discussion pour cette réunion est accessible à cette adresse <a [href]="urlsalon" target="_blank">{{urlsalon}}</a>.<BR>
|
||||
<span *ngIf="urlpad">Un pad a été créé pour cette réunion <a [href]="urlpad" target="_blank">{{urlpad}}</a>.</span><BR>
|
||||
<BR>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="footer">
|
||||
<div>
|
||||
<p-button label="Back" (onClick)="prevPage1()" icon="pi pi-angle-left"></p-button>
|
||||
</div>
|
||||
|
||||
|
||||
</ng-template>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CreatePollComponentComponent } from './create-poll-component.component';
|
||||
|
||||
describe('CreatePollComponentComponent', () => {
|
||||
let component: CreatePollComponentComponent;
|
||||
let fixture: ComponentFixture<CreatePollComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ CreatePollComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CreatePollComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,387 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MenuItem, MessageService } from 'primeng/api';
|
||||
import { PollService } from '../poll-service.service';
|
||||
import { FullCalendarComponent } from '@fullcalendar/angular';
|
||||
import frLocale from '@fullcalendar/core/locales/fr';
|
||||
import { PollChoice, Poll, User } from '../model/model';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CalendarOptions, EventInput } from '@fullcalendar/core';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||
|
||||
/*FullCalendarModule.registerPlugins([ // register FullCalendar plugins
|
||||
dayGridPlugin,
|
||||
interactionPlugin,
|
||||
timeGridPlugin
|
||||
]);*/
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-poll-component',
|
||||
templateUrl: './create-poll-component.component.html',
|
||||
styleUrls: ['./create-poll-component.component.css'],
|
||||
providers: [MessageService, PollService, FullCalendarComponent]
|
||||
})
|
||||
export class CreatePollComponentComponent implements OnInit {
|
||||
urlsondage = '';
|
||||
urlsondageadmin = '';
|
||||
urlsalon = '';
|
||||
urlpad = '';
|
||||
|
||||
items: MenuItem[];
|
||||
options: CalendarOptions;
|
||||
|
||||
step = 0;
|
||||
|
||||
slugid: string;
|
||||
poll: Poll = {};
|
||||
|
||||
events: EventInput[] = [];
|
||||
eventsfromics: EventInput[] = [];
|
||||
allevents: EventInput[] = [];
|
||||
|
||||
|
||||
calendarComponent: FullCalendarComponent;
|
||||
hasics = false;
|
||||
loadics = false;
|
||||
ics: string;
|
||||
|
||||
@ViewChild('calendar') set content(content: FullCalendarComponent) {
|
||||
if (content) { // initially setter gets called with undefined
|
||||
this.calendarComponent = content;
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
|
||||
this.poll.pollChoices.forEach(pc => {
|
||||
|
||||
const evt =
|
||||
{
|
||||
title: '',
|
||||
start: pc.startDate,
|
||||
end: pc.endDate,
|
||||
resourceEditable: false,
|
||||
eventResizableFromStart: false,
|
||||
extendedProps: {
|
||||
choiceid: pc.id,
|
||||
tmpId: this.getUniqueId(8)
|
||||
},
|
||||
};
|
||||
this.events.push(evt);
|
||||
calendarApi.addEvent(evt, true);
|
||||
|
||||
});
|
||||
calendarApi.setOption('validRange', {
|
||||
start: this.getValidDate(),
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
submitted = false;
|
||||
|
||||
|
||||
constructor(public messageService: MessageService, public pollService: PollService, private actRoute: ActivatedRoute) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.poll.pollChoices = [];
|
||||
this.items = [{
|
||||
label: 'Informations pour le rendez vous',
|
||||
command: () => {
|
||||
this.step = 0;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Choix de la date',
|
||||
command: () => {
|
||||
this.step = 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Résumé',
|
||||
command: () => {
|
||||
this.step = 2;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
|
||||
this.options = {
|
||||
initialView: 'timeGridWeek',
|
||||
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin],
|
||||
|
||||
// dateClick: this.handleDateClick.bind(this), // bind is important!
|
||||
select: (selectionInfo) => {
|
||||
console.log(selectionInfo);
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
console.log(this.getUniqueId(8));
|
||||
const evt = {
|
||||
title: '',
|
||||
start: selectionInfo.start,
|
||||
end: selectionInfo.end,
|
||||
resourceEditable: true,
|
||||
eventResizableFromStart: true,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
extendedProps: {
|
||||
// tmpId: this.getUniqueId(8)
|
||||
},
|
||||
};
|
||||
calendarApi.addEvent(evt, true);
|
||||
this.events.push(evt);
|
||||
this.allevents.push(evt);
|
||||
},
|
||||
|
||||
events: this.allevents,
|
||||
editable: true,
|
||||
droppable: true,
|
||||
// selectMirror: true,
|
||||
eventResizableFromStart: true,
|
||||
selectable: true,
|
||||
locale: frLocale,
|
||||
themeSystem: 'bootstrap',
|
||||
slotMinTime: '08:00:00',
|
||||
slotMaxTime: '20:00:00',
|
||||
eventMouseEnter: (mouseEnterInfo) => {
|
||||
|
||||
},
|
||||
eventDrop: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
evt.start = info.event.start;
|
||||
evt.end = info.event.end;
|
||||
},
|
||||
eventResize: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
const index = this.events.indexOf(evt);
|
||||
evt.start = info.event.start;
|
||||
evt.end = info.event.end;
|
||||
},
|
||||
eventClick: (info) => {
|
||||
const evt = this.events.filter(e => e.id === info.event.id).pop();
|
||||
if (evt != null){
|
||||
const index = this.events.indexOf(evt);
|
||||
if (index > -1) {
|
||||
this.events.splice(index, 1);
|
||||
}
|
||||
const index1 = this.allevents.indexOf(evt);
|
||||
if (index1 > -1) {
|
||||
this.allevents.splice(index1, 1);
|
||||
}
|
||||
info.event.remove();
|
||||
}
|
||||
|
||||
},
|
||||
validRange: {
|
||||
start: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
this.actRoute.paramMap.subscribe(params => {
|
||||
this.slugid = params.get('slugadminid');
|
||||
console.log(this.slugid);
|
||||
|
||||
if (this.slugid != null) {
|
||||
|
||||
this.pollService.getPollBySlugAdminId(this.slugid).subscribe(p => {
|
||||
if (p != null) {
|
||||
this.poll = p;
|
||||
} else {
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Un sondage avec cet identifiant n\'existe pas',
|
||||
detail: 'Le sondage n\'a pas été récupéré'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
nextPage(): void {
|
||||
|
||||
if (this.poll.title && this.poll.location && this.poll.description) {
|
||||
this.step = 1;
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Données incomplètes',
|
||||
detail: 'Veuillez remplir les champs requis'
|
||||
}
|
||||
);
|
||||
|
||||
this.submitted = true;
|
||||
}
|
||||
|
||||
nextPage1(): void {
|
||||
console.log(this.poll.id);
|
||||
if (this.poll.id == null) {
|
||||
this.events.forEach(e => {
|
||||
this.poll.pollChoices.push({
|
||||
startDate: e.start as any,
|
||||
endDate: e.end as any,
|
||||
});
|
||||
});
|
||||
this.pollService.createPoll(this.poll).subscribe(p1 => {
|
||||
this.poll = p1;
|
||||
this.urlsondage = window.location.protocol + '//' + window.location.host + '/answer/' + p1.slug;
|
||||
this.urlsondageadmin = window.location.protocol + '//' + window.location.host + '/admin/' + p1.slugAdmin;
|
||||
this.urlsalon = p1.tlkURL;
|
||||
this.urlpad = p1.padURL;
|
||||
this.step = 2;
|
||||
});
|
||||
} else {
|
||||
|
||||
const toKeep: PollChoice[] = [];
|
||||
this.events.filter(c => c.extendedProps != null && c.extendedProps.choiceid != null).forEach(e => {
|
||||
toKeep.push(this.poll.pollChoices.filter(c1 => c1.id === e.extendedProps.choiceid)[0]);
|
||||
});
|
||||
this.poll.pollChoices = toKeep;
|
||||
this.poll.pollChoices.forEach(c => {
|
||||
const res = this.events.filter(c1 => c1.extendedProps != null &&
|
||||
c1.extendedProps.choiceid != null && c1.extendedProps.choiceid === c.id)[0];
|
||||
c.startDate = res.start as any;
|
||||
c.endDate = res.end as any;
|
||||
});
|
||||
|
||||
this.events.filter(c => c.extendedProps == null || c.extendedProps.choiceid == null).forEach(e => {
|
||||
this.poll.pollChoices.push({
|
||||
startDate: e.start as any,
|
||||
endDate: e.end as any,
|
||||
});
|
||||
});
|
||||
console.log(this.events);
|
||||
console.log(this.poll.pollChoices);
|
||||
|
||||
this.pollService.updtatePoll(this.poll).subscribe(p1 => {
|
||||
this.poll = p1;
|
||||
this.urlsondage = 'http://localhost:4200/answer/' + p1.slug;
|
||||
this.urlsondageadmin = 'http://localhost:4200/admin/' + p1.slugAdmin;
|
||||
this.urlsalon = p1.tlkURL;
|
||||
this.urlpad = p1.padURL;
|
||||
this.step = 2;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
prevPage1(): void {
|
||||
|
||||
this.step = this.step - 1;
|
||||
}
|
||||
|
||||
|
||||
private getUniqueId(parts: number): string {
|
||||
const stringArr = [];
|
||||
for (let i = 0; i < parts; i++) {
|
||||
// tslint:disable-next-line:no-bitwise
|
||||
const S4 = (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||
stringArr.push(S4);
|
||||
}
|
||||
return stringArr.join('-');
|
||||
}
|
||||
|
||||
private getValidDate(): number {
|
||||
if (this.poll.id != null) {
|
||||
if ((this.poll.pollChoices[0].startDate as any - Date.now()) < 0) {
|
||||
return this.poll.pollChoices[0].startDate as any;
|
||||
}
|
||||
}
|
||||
return Date.now();
|
||||
|
||||
}
|
||||
|
||||
|
||||
getICS(): void {
|
||||
this.loadics = true;
|
||||
this.pollService.getICS(this.slugid, this.ics).subscribe(res => {
|
||||
this.loadics = false;
|
||||
|
||||
const calendarApi = this.calendarComponent.getApi();
|
||||
if (res.eventdtos.length > 0) {
|
||||
this.eventsfromics.forEach(eid => {
|
||||
const index = this.allevents.indexOf(eid);
|
||||
if (index > -1) {
|
||||
this.allevents.splice(index, 1);
|
||||
}
|
||||
calendarApi.getEventById(eid.id)?.remove();
|
||||
});
|
||||
this.eventsfromics = [];
|
||||
}
|
||||
console.log(res);
|
||||
|
||||
res.eventdtos.forEach(evtdto => { // calendarApi.next();
|
||||
const evt1 =
|
||||
{
|
||||
title: evtdto.description,
|
||||
start: evtdto.startDate,
|
||||
end: evtdto.endDate,
|
||||
resourceEditable: false,
|
||||
editable: false,
|
||||
droppable: false,
|
||||
selectable: false,
|
||||
eventResizableFromStart: false,
|
||||
id: this.getUniqueId(8),
|
||||
|
||||
backgroundColor: 'red',
|
||||
extendedProps: {
|
||||
fromics: true
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
const eventAPI = calendarApi.addEvent(evt1, true);
|
||||
this.eventsfromics.push(evt1);
|
||||
this.allevents.push(evt1);
|
||||
|
||||
});
|
||||
|
||||
const unselected = this.events.map(ev => ev.extendedProps.choiceid);
|
||||
res.selectedChoices.forEach(e => {
|
||||
const index = unselected.indexOf(e);
|
||||
if (index > -1) {
|
||||
unselected.splice(index, 1);
|
||||
}
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'red';
|
||||
evt1.extendedProps.selected = false;
|
||||
evt2.setProp('backgroundColor', 'red');
|
||||
// this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
unselected.forEach(e => {
|
||||
const evt1 = this.events.filter(ev => ev.extendedProps.choiceid === e)[0];
|
||||
|
||||
const evt2 = calendarApi.getEventById(evt1.id);
|
||||
evt1.backgroundColor = 'green';
|
||||
evt1.extendedProps.selected = true;
|
||||
evt2.setProp('backgroundColor', 'green');
|
||||
this.poll.pollChoices.filter(pc => pc.id === evt1.extendedProps.choiceid)[0].users.push({ id: -1 });
|
||||
});
|
||||
}, (err) => {
|
||||
this.loadics = false;
|
||||
|
||||
this.messageService.add(
|
||||
{
|
||||
severity: 'warn',
|
||||
summary: 'Ne peut récupérer l\'agenda à partir de l\'adresse de l\'ics',
|
||||
detail: 'Une erreur s\'est produite au moment de la récupération de l\'agenda'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DateagoPipe } from './dateago.pipe';
|
||||
|
||||
describe('DateagoPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new DateagoPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
41
ansible/files/doodlestudent/front/src/app/dateago.pipe.ts
Normal file
41
ansible/files/doodlestudent/front/src/app/dateago.pipe.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'dateago',
|
||||
pure: true
|
||||
|
||||
})
|
||||
export class DateagoPipe implements PipeTransform {
|
||||
|
||||
transform(value: any, args?: any): any {
|
||||
if (value) {
|
||||
const seconds = Math.floor((+new Date() - +new Date(value)) / 1000);
|
||||
if (seconds < 29) { // less than 30 seconds ago will show as 'Just now'
|
||||
return 'quelques secondes';
|
||||
}
|
||||
const intervals = {
|
||||
année: 31536000,
|
||||
mois: 2592000,
|
||||
semaine: 604800,
|
||||
jour: 86400,
|
||||
heure: 3600,
|
||||
minute: 60,
|
||||
seconde: 1
|
||||
};
|
||||
let counter;
|
||||
// tslint:disable-next-line:forin
|
||||
for (const i in intervals) {
|
||||
counter = Math.floor(seconds / intervals[i]);
|
||||
if (counter > 0){
|
||||
if (counter === 1 || i === 'mois') {
|
||||
return counter + ' ' + i + ''; // singular (1 day ago)
|
||||
} else {
|
||||
return counter + ' ' + i + 's'; // plural (2 days ago)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export class Card {
|
||||
constructor(public image: string, public style: any, public title: string, public subtitle: string){
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
.Container {
|
||||
max-width: 800px;
|
||||
margin: 2rem auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Home_Container {
|
||||
width: 1024px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Home_Wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Home_Logo {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.Home_Button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.Home_CreateLink {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
background-color: #43dbac;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 1.3rem;
|
||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.10);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<div class="Home_Container">
|
||||
<div class="Home_Wrapper">
|
||||
<div class="Home_Logo">
|
||||
<img src="../../assets/Logo.png" alt="Logo Simba" height="130px"/>
|
||||
</div>
|
||||
<app-card-small-component [cards]="cards"></app-card-small-component>
|
||||
<div class="Home_Button">
|
||||
<a routerLink="/create" class="Home_CreateLink">Créer votre poll !</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponentComponent } from './home-component.component';
|
||||
|
||||
describe('HomeComponentComponent', () => {
|
||||
let component: HomeComponentComponent;
|
||||
let fixture: ComponentFixture<HomeComponentComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ HomeComponentComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponentComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CardSmallComponentComponent } from '../card-small-component/card-small-component.component';
|
||||
import {Card} from './Card';
|
||||
@Component({
|
||||
selector: 'app-home-component',
|
||||
templateUrl: './home-component.component.html',
|
||||
styleUrls: ['./home-component.component.css'],
|
||||
providers: [CardSmallComponentComponent]
|
||||
|
||||
})
|
||||
export class HomeComponentComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
|
||||
cards: Card[] = [];
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.cards.push(new Card('assets/1.png', {backgroundColor: '#44baf2', color: 'white'}, 'Créez un sondage', 'Définissez plusieurs créneaux pour votre réunion.'));
|
||||
this.cards.push(new Card('assets/2.png', {backgroundColor: '#fc506d', color: 'white'}, 'Envoyez vos invitations', 'Les participants aux sondages pourront voter pour les dates qui leur conviennent le mieux !'));
|
||||
this.cards.push(new Card('assets/3.png', {backgroundColor: '#8f3ee8', color: 'white'}, 'Faites votre choix', 'Vous pourrez obtenir en direct les résultats du sondage afin de choisir au mieux la meilleure proposition.'));
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header text-center justify-content-center">
|
||||
<h4 class="modal-title" id="modal-basic-title">Le sondage est maintenant clos</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
La date retenue pour le soundage est le:
|
||||
</div>
|
||||
<BR>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b>{{poll?.selectedChoice?.startDate | date:'EEEE d LLLL': 'CEST':'fr'}}</b>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> de {{poll?.selectedChoice?.startDate | date:'H:mm'}}</b>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> - </b>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center">
|
||||
<b> {{poll?.selectedChoice?.endDate | date:'H:mm'}}</b>
|
||||
</div>
|
||||
<BR>
|
||||
Le lieu sera {{poll?.location}}
|
||||
<BR>
|
||||
<div *ngIf="poll?.padURL">
|
||||
Un pad est ouvert <a [href]="poll?.padURL" target="_blank">ici</a>
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.tlkURL">
|
||||
Un salon de discussion est ouvert <a [href]="poll?.tlkURL" target="_blank">ici</a>
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.has_meal">Un repas est prévu pour ce meeting.
|
||||
</div>
|
||||
<BR>
|
||||
<div *ngIf="poll?.description">
|
||||
L'ordre du jour est le suivant:
|
||||
{{poll.description}}
|
||||
</div>
|
||||
<BR>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-dark" (click)="dismissModalAndNavigate()">Retour</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ModalPollClosComponent } from './modal-poll-clos.component';
|
||||
|
||||
describe('ModalPollClosComponent', () => {
|
||||
let component: ModalPollClosComponent;
|
||||
let fixture: ComponentFixture<ModalPollClosComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ModalPollClosComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ModalPollClosComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Poll } from '../model/model';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal-poll-clos',
|
||||
templateUrl: './modal-poll-clos.component.html',
|
||||
styleUrls: ['./modal-poll-clos.component.css'],
|
||||
providers: [NgbActiveModal]
|
||||
})
|
||||
export class ModalPollClosComponent implements OnInit {
|
||||
|
||||
@Input() poll: Poll;
|
||||
constructor(public activeModal: NgbActiveModal, public router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
dismissModalAndNavigate(): void{
|
||||
this.activeModal.close();
|
||||
window.location.href = '/';
|
||||
|
||||
}
|
||||
}
|
||||
57
ansible/files/doodlestudent/front/src/app/model/model.ts
Normal file
57
ansible/files/doodlestudent/front/src/app/model/model.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export interface Poll {
|
||||
createdAt?: Date;
|
||||
description?: string;
|
||||
has_meal?: boolean;
|
||||
id?: number;
|
||||
location?: string;
|
||||
padURL?: string;
|
||||
pollChoices?: PollChoice[];
|
||||
selectedChoice ?: PollChoice;
|
||||
pollComments?: PollCommentElement[];
|
||||
pollMealPreferences?: PollCommentElement[];
|
||||
slug?: string;
|
||||
slugAdmin?: string;
|
||||
title?: string;
|
||||
tlkURL?: string;
|
||||
updatedAt?: Date;
|
||||
clos ?: boolean;
|
||||
}
|
||||
|
||||
export interface PollChoice {
|
||||
endDate?: Date;
|
||||
id?: number;
|
||||
startDate?: Date;
|
||||
users?: User[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: number;
|
||||
username?: string;
|
||||
mail?: string;
|
||||
}
|
||||
|
||||
export interface ChoiceUser {
|
||||
username?: string;
|
||||
mail?: string;
|
||||
pref?: string;
|
||||
ics?: string;
|
||||
choices?: number[];
|
||||
}
|
||||
|
||||
export interface PollCommentElement {
|
||||
content?: string;
|
||||
id?: number;
|
||||
auteur?: string;
|
||||
}
|
||||
|
||||
export interface EventDTO{
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface EventDTOAndSelectedChoice {
|
||||
eventdtos?: EventDTO[];
|
||||
selectedChoices?: number[];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PollServiceService } from './poll-service.service';
|
||||
|
||||
describe('PollServiceService', () => {
|
||||
let service: PollServiceService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PollServiceService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Poll, PollChoice, User, ChoiceUser, PollCommentElement, EventDTOAndSelectedChoice } from './model/model';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PollService {
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public createPoll(p: Poll): Observable<Poll> {
|
||||
console.log('create poll');
|
||||
return this.http.post<Poll>('/api/polls', p);
|
||||
}
|
||||
|
||||
|
||||
public updtatePoll(p: Poll): Observable<Poll> {
|
||||
return this.http.put<Poll>('/api/poll/update1', p);
|
||||
}
|
||||
|
||||
|
||||
public getPollBySlugId(slugId: string): Observable<Poll>{
|
||||
return this.http.get<Poll>('/api/poll/slug/' + slugId);
|
||||
}
|
||||
|
||||
public getComentsBySlugId(slugId: string): Observable<PollCommentElement[]>{
|
||||
return this.http.get<PollCommentElement[]>('/api/polls/' + slugId + '/comments');
|
||||
}
|
||||
|
||||
public getPollBySlugAdminId(slugId: string): Observable<Poll>{
|
||||
return this.http.get<Poll>('/api/poll/aslug/' + slugId);
|
||||
|
||||
}
|
||||
|
||||
public updateChoice4user( cu: ChoiceUser): Observable<User>{
|
||||
|
||||
return this.http.post<User>('/api/poll/choiceuser/', cu);
|
||||
}
|
||||
|
||||
public addComment4Poll( slug: string, comment: PollCommentElement ): Observable<PollCommentElement>{
|
||||
|
||||
return this.http.post<PollCommentElement>('/api/poll/comment/' + slug, comment);
|
||||
}
|
||||
|
||||
selectEvent(choiceid: number): Observable<void> {
|
||||
return this.http.post<void>('/api/poll/selectedchoice/' + choiceid, null);
|
||||
|
||||
}
|
||||
|
||||
getICS(slug: string, ics: string): Observable<EventDTOAndSelectedChoice> {
|
||||
return this.http.get<EventDTOAndSelectedChoice>('/api/ics/polls/' + slug + '/' + btoa(ics));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Selecteddate4userPipePipe } from './selecteddate4user-pipe.pipe';
|
||||
|
||||
describe('Selecteddate4userPipePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new Selecteddate4userPipePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user