Первая версия базовых компонент
This commit is contained in:
commit
201d604ca2
|
|
@ -0,0 +1,5 @@
|
|||
.idea
|
||||
|
||||
**/target
|
||||
|
||||
**/node_modules
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [2024] Dzyk Anton
|
||||
|
||||
Licensed 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.
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
Базовые компоненты обучающего портала DevOps-практик
|
||||
====================================================
|
||||
|
||||
Данный проект содержит базовые компоненты для построения обучающего портала DevOps-практикам.
|
||||
|
||||
Стек
|
||||
----
|
||||
|
||||
1. API Server
|
||||
* Java 21, Spring Boot 3
|
||||
2. Engine (Docker)
|
||||
* Golang 1.21
|
||||
3. Frontend
|
||||
* Vue.js, axios
|
||||
|
||||
|
||||
Связанные сервисы
|
||||
-----------------
|
||||
|
||||
1. Keycloak
|
||||
2. PostgreSQL
|
||||
3. Kafka
|
||||
|
||||
|
||||
Сборка
|
||||
------
|
||||
|
||||
```shell
|
||||
cd src/backend/api-server
|
||||
docker build -t api-server .
|
||||
```
|
||||
|
||||
```shell
|
||||
cd src/backend/engines/docker
|
||||
docker build -t docker-engine .
|
||||
```
|
||||
|
||||
```shell
|
||||
cd src/frontend/user-frontend
|
||||
docker build -t user-frontend .
|
||||
```
|
||||
|
||||
Локальная разработка
|
||||
--------------------
|
||||
|
||||
Для разработки необходимо [запустить](./debug/Readme.md) связанные сервисы.
|
||||
|
||||
|
||||
Лицензия
|
||||
--------
|
||||
|
||||
Данный проект распространяется под лицензией [Apache 2.0](./LICENSE)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
KEYCLOAK_VERSION=24.0.5
|
||||
KAFKA_IMAGE_VERSION=7.7.1
|
||||
POSTGRESQL_VERSION=16
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
Запуск окружения для разработки
|
||||
===============================
|
||||
|
||||
|
||||
```shell
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Пересоздание стенда
|
||||
```shell
|
||||
docker compose down
|
||||
docker volume rm education_system
|
||||
docker compose up -d
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
spring.datasource.url=jdbc:postgresql://localhost:5432/education_system
|
||||
spring.datasource.username=postgres
|
||||
spring.datasource.password=postgres
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Keycloak
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE USER keycloak PASSWORD 'keycloak';
|
||||
CREATE DATABASE keycloak OWNER keycloak;
|
||||
EOSQL
|
||||
|
||||
# API Server
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE USER education_system PASSWORD 'education_system';
|
||||
CREATE DATABASE education_system OWNER education_system;
|
||||
EOSQL
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
name: education-system
|
||||
|
||||
services:
|
||||
postgres:
|
||||
container_name: edu_postgres
|
||||
image: postgres:${POSTGRESQL_VERSION}
|
||||
restart: always
|
||||
volumes:
|
||||
- education_system_postgres:/var/lib/postgresql/data
|
||||
- ./configs/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh
|
||||
ports:
|
||||
- "5432:5432"
|
||||
networks:
|
||||
- education_system
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
||||
keycloak:
|
||||
container_name: edu_keycloak
|
||||
restart: on-failure:5
|
||||
image: quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}
|
||||
environment:
|
||||
KC_LOG_LEVEL: debug
|
||||
KC_DB: postgres
|
||||
KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak'
|
||||
KC_DB_USERNAME: keycloak
|
||||
KC_DB_PASSWORD: keycloak
|
||||
KC_DB_SCHEMA: public
|
||||
KC_HOSTNAME: portal.local
|
||||
KC_HOSTNAME_PATH: keycloak
|
||||
KC_HTTP_RELATIVE_PATH: keycloak
|
||||
KC_HTTP_ENABLED: true
|
||||
HTTP_ADDRESS_FORWARDING: true
|
||||
KEYCLOAK_ADMIN: admin
|
||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
||||
ports:
|
||||
- '8080:8080'
|
||||
command: start-dev
|
||||
networks:
|
||||
- education_system
|
||||
depends_on:
|
||||
- postgres
|
||||
|
||||
zookeeper:
|
||||
image: confluentinc/cp-zookeeper:${KAFKA_IMAGE_VERSION}
|
||||
container_name: edu_zookeeper
|
||||
environment:
|
||||
ZOOKEEPER_CLIENT_PORT: 2181
|
||||
ZOOKEEPER_TICK_TIME: 2000
|
||||
ports:
|
||||
- "2181:2181"
|
||||
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:${KAFKA_IMAGE_VERSION}
|
||||
container_name: edu_kafka
|
||||
depends_on:
|
||||
- zookeeper
|
||||
ports:
|
||||
- "29092:29092"
|
||||
environment:
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
|
||||
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
||||
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
|
||||
init-kafka:
|
||||
container_name: edu_init_kafka
|
||||
image: confluentinc/cp-kafka:${KAFKA_IMAGE_VERSION}
|
||||
depends_on:
|
||||
- kafka
|
||||
entrypoint: [ '/bin/sh', '-c' ]
|
||||
command: |
|
||||
"
|
||||
kafka-topics --bootstrap-server kafka:9092 --create --if-not-exists --topic edu_task_docker --replication-factor 1 --partitions 1
|
||||
kafka-topics --bootstrap-server kafka:9092 --create --if-not-exists --topic edu_results --replication-factor 1 --partitions 1
|
||||
|
||||
kafka-topics --bootstrap-server kafka:9092 --list
|
||||
"
|
||||
|
||||
kafka-ui:
|
||||
container_name: edu_kafka_ui
|
||||
image: provectuslabs/kafka-ui:v0.7.2
|
||||
ports:
|
||||
- 8001:8080
|
||||
depends_on:
|
||||
- kafka
|
||||
- zookeeper
|
||||
environment:
|
||||
KAFKA_CLUSTERS_0_NAME: local
|
||||
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
|
||||
KAFKA_CLUSTERS_0_METRICS_PORT: 9997
|
||||
|
||||
volumes:
|
||||
education_system_postgres:
|
||||
name: education_system
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
education_system:
|
||||
name: education-system
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
# 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
|
||||
#
|
||||
# https://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.
|
||||
wrapperVersion=3.3.2
|
||||
distributionType=only-script
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
#!/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
|
||||
#
|
||||
# https://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.3.2
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
|
||||
# MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
set -euf
|
||||
[ "${MVNW_VERBOSE-}" != debug ] || set -x
|
||||
|
||||
# OS specific support.
|
||||
native_path() { printf %s\\n "$1"; }
|
||||
case "$(uname)" in
|
||||
CYGWIN* | MINGW*)
|
||||
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
|
||||
native_path() { cygpath --path --windows "$1"; }
|
||||
;;
|
||||
esac
|
||||
|
||||
# set JAVACMD and JAVACCMD
|
||||
set_java_home() {
|
||||
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
|
||||
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"
|
||||
JAVACCMD="$JAVA_HOME/jre/sh/javac"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACCMD="$JAVA_HOME/bin/javac"
|
||||
|
||||
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
|
||||
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
|
||||
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
JAVACMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v java
|
||||
)" || :
|
||||
JAVACCMD="$(
|
||||
'set' +e
|
||||
'unset' -f command 2>/dev/null
|
||||
'command' -v javac
|
||||
)" || :
|
||||
|
||||
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
|
||||
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# hash string like Java String::hashCode
|
||||
hash_string() {
|
||||
str="${1:-}" h=0
|
||||
while [ -n "$str" ]; do
|
||||
char="${str%"${str#?}"}"
|
||||
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
|
||||
str="${str#?}"
|
||||
done
|
||||
printf %x\\n $h
|
||||
}
|
||||
|
||||
verbose() { :; }
|
||||
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
|
||||
|
||||
die() {
|
||||
printf %s\\n "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
trim() {
|
||||
# MWRAPPER-139:
|
||||
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
|
||||
# Needed for removing poorly interpreted newline sequences when running in more
|
||||
# exotic environments such as mingw bash on Windows.
|
||||
printf "%s" "${1}" | tr -d '[:space:]'
|
||||
}
|
||||
|
||||
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
|
||||
while IFS="=" read -r key value; do
|
||||
case "${key-}" in
|
||||
distributionUrl) distributionUrl=$(trim "${value-}") ;;
|
||||
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
|
||||
esac
|
||||
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
|
||||
|
||||
case "${distributionUrl##*/}" in
|
||||
maven-mvnd-*bin.*)
|
||||
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
|
||||
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
|
||||
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
|
||||
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
|
||||
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
|
||||
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
|
||||
*)
|
||||
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
|
||||
distributionPlatform=linux-amd64
|
||||
;;
|
||||
esac
|
||||
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
|
||||
;;
|
||||
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
|
||||
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
|
||||
esac
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
distributionUrlNameMain="${distributionUrlName%.*}"
|
||||
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
|
||||
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
|
||||
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
|
||||
|
||||
exec_maven() {
|
||||
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
|
||||
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
|
||||
}
|
||||
|
||||
if [ -d "$MAVEN_HOME" ]; then
|
||||
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
exec_maven "$@"
|
||||
fi
|
||||
|
||||
case "${distributionUrl-}" in
|
||||
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
|
||||
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
|
||||
esac
|
||||
|
||||
# prepare tmp dir
|
||||
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
|
||||
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
|
||||
trap clean HUP INT TERM EXIT
|
||||
else
|
||||
die "cannot create temp dir"
|
||||
fi
|
||||
|
||||
mkdir -p -- "${MAVEN_HOME%/*}"
|
||||
|
||||
# Download and Install Apache Maven
|
||||
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
verbose "Downloading from: $distributionUrl"
|
||||
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
# select .zip or .tar.gz
|
||||
if ! command -v unzip >/dev/null; then
|
||||
distributionUrl="${distributionUrl%.zip}.tar.gz"
|
||||
distributionUrlName="${distributionUrl##*/}"
|
||||
fi
|
||||
|
||||
# verbose opt
|
||||
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
|
||||
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
|
||||
|
||||
# normalize http auth
|
||||
case "${MVNW_PASSWORD:+has-password}" in
|
||||
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
|
||||
esac
|
||||
|
||||
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
|
||||
verbose "Found wget ... using wget"
|
||||
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
|
||||
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
|
||||
verbose "Found curl ... using curl"
|
||||
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
|
||||
elif set_java_home; then
|
||||
verbose "Falling back to use Java to download"
|
||||
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
|
||||
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
cat >"$javaSource" <<-END
|
||||
public class Downloader extends java.net.Authenticator
|
||||
{
|
||||
protected java.net.PasswordAuthentication getPasswordAuthentication()
|
||||
{
|
||||
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
|
||||
}
|
||||
public static void main( String[] args ) throws Exception
|
||||
{
|
||||
setDefault( new Downloader() );
|
||||
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
|
||||
}
|
||||
}
|
||||
END
|
||||
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
|
||||
verbose " - Compiling Downloader.java ..."
|
||||
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
|
||||
verbose " - Running Downloader.java ..."
|
||||
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
|
||||
fi
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
if [ -n "${distributionSha256Sum-}" ]; then
|
||||
distributionSha256Result=false
|
||||
if [ "$MVN_CMD" = mvnd.sh ]; then
|
||||
echo "Checksum validation is not supported for maven-mvnd." >&2
|
||||
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
elif command -v sha256sum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
elif command -v shasum >/dev/null; then
|
||||
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
|
||||
distributionSha256Result=true
|
||||
fi
|
||||
else
|
||||
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
|
||||
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ $distributionSha256Result = false ]; then
|
||||
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
|
||||
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# unzip and move
|
||||
if command -v unzip >/dev/null; then
|
||||
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
|
||||
else
|
||||
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
|
||||
fi
|
||||
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
|
||||
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
|
||||
|
||||
clean || :
|
||||
exec_maven "$@"
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<# : batch portion
|
||||
@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 https://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.3.2
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM MVNW_REPOURL - repo url base for downloading maven distribution
|
||||
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
|
||||
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
|
||||
@SET __MVNW_CMD__=
|
||||
@SET __MVNW_ERROR__=
|
||||
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
|
||||
@SET PSModulePath=
|
||||
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
|
||||
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
|
||||
)
|
||||
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
|
||||
@SET __MVNW_PSMODULEP_SAVE=
|
||||
@SET __MVNW_ARG0_NAME__=
|
||||
@SET MVNW_USERNAME=
|
||||
@SET MVNW_PASSWORD=
|
||||
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
|
||||
@echo Cannot start maven from wrapper >&2 && exit /b 1
|
||||
@GOTO :EOF
|
||||
: end batch / begin powershell #>
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ($env:MVNW_VERBOSE -eq "true") {
|
||||
$VerbosePreference = "Continue"
|
||||
}
|
||||
|
||||
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
|
||||
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
|
||||
if (!$distributionUrl) {
|
||||
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
|
||||
}
|
||||
|
||||
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
|
||||
"maven-mvnd-*" {
|
||||
$USE_MVND = $true
|
||||
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
|
||||
$MVN_CMD = "mvnd.cmd"
|
||||
break
|
||||
}
|
||||
default {
|
||||
$USE_MVND = $false
|
||||
$MVN_CMD = $script -replace '^mvnw','mvn'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# apply MVNW_REPOURL and calculate MAVEN_HOME
|
||||
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
|
||||
if ($env:MVNW_REPOURL) {
|
||||
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
|
||||
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
|
||||
}
|
||||
$distributionUrlName = $distributionUrl -replace '^.*/',''
|
||||
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
|
||||
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
|
||||
if ($env:MAVEN_USER_HOME) {
|
||||
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
|
||||
}
|
||||
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
|
||||
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
|
||||
|
||||
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
|
||||
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
exit $?
|
||||
}
|
||||
|
||||
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
|
||||
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
|
||||
}
|
||||
|
||||
# prepare tmp dir
|
||||
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
|
||||
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
|
||||
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
|
||||
trap {
|
||||
if ($TMP_DOWNLOAD_DIR.Exists) {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
}
|
||||
|
||||
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
|
||||
|
||||
# Download and Install Apache Maven
|
||||
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
|
||||
Write-Verbose "Downloading from: $distributionUrl"
|
||||
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
|
||||
|
||||
$webclient = New-Object System.Net.WebClient
|
||||
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
|
||||
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
|
||||
}
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
|
||||
|
||||
# If specified, validate the SHA-256 sum of the Maven distribution zip file
|
||||
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
|
||||
if ($distributionSha256Sum) {
|
||||
if ($USE_MVND) {
|
||||
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
|
||||
}
|
||||
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
|
||||
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
|
||||
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
|
||||
}
|
||||
}
|
||||
|
||||
# unzip and move
|
||||
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
|
||||
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
|
||||
try {
|
||||
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
|
||||
} catch {
|
||||
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
|
||||
Write-Error "fail to move MAVEN_HOME"
|
||||
}
|
||||
} finally {
|
||||
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
|
||||
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
|
||||
}
|
||||
|
||||
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.oa2.edu</groupId>
|
||||
<artifactId>edu-api-server</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>ApiServer</name>
|
||||
<description>Education System</description>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
|
||||
<spring-boot.version>3.3.3</spring-boot.version>
|
||||
<springframework.version>6.1.12</springframework.version>
|
||||
<spring-security-test.version>6.3.3</spring-security-test.version>
|
||||
<keycloak.version>24.0.5</keycloak.version>
|
||||
<spring-kafka.version>3.2.3</spring-kafka.version>
|
||||
|
||||
<postgresql.version>42.7.2</postgresql.version>
|
||||
<jakarta.annotation-api.version>2.1.1</jakarta.annotation-api.version>
|
||||
<jakarta.validation-api.version>3.1.0</jakarta.validation-api.version>
|
||||
<lombok.version>1.18.34</lombok.version>
|
||||
<junit-jupiter.version>5.10.3</junit-jupiter.version>
|
||||
|
||||
<maven-compiler-plugin.version>3.13.0</maven-compiler-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-spring-security-adapter</artifactId>
|
||||
<version>${keycloak.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.annotation</groupId>
|
||||
<artifactId>jakarta.annotation-api</artifactId>
|
||||
<version>${jakarta.annotation-api.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>${jakarta.validation-api.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.kafka</groupId>
|
||||
<artifactId>spring-kafka</artifactId>
|
||||
<version>${spring-kafka.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.17.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>${springframework.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>${spring-security-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<parameters>true</parameters>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package ru.oa2.edu.api.application;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages={"ru.oa2.edu.api.*"})
|
||||
@EnableJpaRepositories(basePackages={
|
||||
"ru.oa2.edu.api.application.database",
|
||||
"ru.oa2.edu.api.domain.job"
|
||||
})
|
||||
@EnableTransactionManagement
|
||||
@EntityScan(basePackages="ru.oa2.edu.api.application.entities")
|
||||
public class ApiServerApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiServerApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.oa2.edu.api.application.config;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
||||
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean
|
||||
public AuthorizationEventPublisher authorizationEventPublisher
|
||||
(ApplicationEventPublisher applicationEventPublisher) {
|
||||
return new SpringAuthorizationEventPublisher(applicationEventPublisher);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.oa2.edu.api.application.config;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
public class CustomSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||
@Override
|
||||
public void onAuthenticationSuccess(final HttpServletRequest request,
|
||||
final HttpServletResponse response, final Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
super.onAuthenticationSuccess(request, response, authentication);
|
||||
|
||||
HttpSession session = request.getSession(true);
|
||||
|
||||
try {
|
||||
var jwtAuth = ((JwtAuthenticationToken) authentication).getTokenAttributes();
|
||||
var sub = (String) jwtAuth.get("sub");
|
||||
log.info(String.format("SUB: %s", sub));
|
||||
} catch (Exception e) {
|
||||
log.error("Error in getting User info", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package ru.oa2.edu.api.application.config;
|
||||
|
||||
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
|
||||
import org.springframework.kafka.core.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
public class KafkaConfig {
|
||||
|
||||
@Value("${kafka.address}")
|
||||
private String kafkaAddress;
|
||||
|
||||
@Bean
|
||||
public ProducerFactory<String, String> producerFactory() {
|
||||
Map<String, Object> configProps = new HashMap<>();
|
||||
configProps.put(
|
||||
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
|
||||
kafkaAddress);
|
||||
configProps.put(
|
||||
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,
|
||||
StringSerializer.class);
|
||||
configProps.put(
|
||||
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,
|
||||
StringSerializer.class);
|
||||
return new DefaultKafkaProducerFactory<>(configProps);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConsumerFactory<String, String> consumerFactory() {
|
||||
Map<String, Object> props = new HashMap<>();
|
||||
props.put(
|
||||
ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
|
||||
kafkaAddress);
|
||||
props.put(
|
||||
ConsumerConfig.GROUP_ID_CONFIG,
|
||||
"edu_results");
|
||||
props.put(
|
||||
ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,
|
||||
StringDeserializer.class);
|
||||
props.put(
|
||||
ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,
|
||||
StringDeserializer.class);
|
||||
return new DefaultKafkaConsumerFactory<>(props);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ConcurrentKafkaListenerContainerFactory<String, String>
|
||||
kafkaListenerContainerFactory() {
|
||||
|
||||
ConcurrentKafkaListenerContainerFactory<String, String> factory =
|
||||
new ConcurrentKafkaListenerContainerFactory<>();
|
||||
factory.setConsumerFactory(consumerFactory());
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public KafkaTemplate<String, String> kafkaTemplate() {
|
||||
return new KafkaTemplate<>(producerFactory());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package ru.oa2.edu.api.application.config;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
import ru.oa2.edu.api.application.database.UserJpaRepository;
|
||||
import ru.oa2.edu.api.domain.user.User;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SaveUser implements AuthorizationEventPublisher {
|
||||
|
||||
@Autowired
|
||||
UserJpaRepository userRepository;
|
||||
|
||||
@Override
|
||||
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {
|
||||
if (userRepository.findByInternalId(authentication.get().getName()) == null) {
|
||||
userRepository.save(new User(authentication.get().getName()));
|
||||
log.info("User with name {} was added", authentication.get().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.oa2.edu.api.application.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount;
|
||||
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.annotation.ScopedProxyMode;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtException;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults()));
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package ru.oa2.edu.api.application.controller;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.oa2.edu.api.domain.task.TaskDTO;
|
||||
import ru.oa2.edu.api.domain.theme.ThemeDTO;
|
||||
import ru.oa2.edu.api.application.services.RepositoryService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class ApiController {
|
||||
|
||||
private final RepositoryService repositoryService;
|
||||
|
||||
public ApiController(RepositoryService repositoryService) {
|
||||
this.repositoryService = repositoryService;
|
||||
}
|
||||
|
||||
@GetMapping()
|
||||
@RequestMapping("/themes")
|
||||
@PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')")
|
||||
public Collection<ThemeDTO> getThemes() {
|
||||
return repositoryService.listThemes();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@GetMapping()
|
||||
@RequestMapping("/task/{id}")
|
||||
@PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')")
|
||||
public TaskDTO getTask(@PathVariable("id") long id) {
|
||||
return repositoryService.getTask(id);
|
||||
}
|
||||
|
||||
@GetMapping("/version")
|
||||
@PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')")
|
||||
public String getVersion() {
|
||||
return "1.0.0";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package ru.oa2.edu.api.application.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.oa2.edu.api.domain.dto.EngineTypes;
|
||||
import ru.oa2.edu.api.domain.dto.Job;
|
||||
import ru.oa2.edu.api.application.services.RunnerService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class RunController {
|
||||
|
||||
@Autowired
|
||||
RunnerService runnerService;
|
||||
|
||||
@PutMapping()
|
||||
@RequestMapping("/run")
|
||||
public ResponseEntity run(@RequestBody Job job) {
|
||||
EngineTypes.DOCKER.toString();
|
||||
switch (EngineTypes.fromString(job.getEngineType())) {
|
||||
case EngineTypes.DOCKER, EngineTypes.DOCKER_BUILDX: {
|
||||
var runId = runnerService.Job("edu_task_docker", job);
|
||||
return ResponseEntity.ok().body(runId);
|
||||
}
|
||||
default: {
|
||||
var res = ResponseEntity.badRequest();
|
||||
res.body("not support engine");
|
||||
return res.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping()
|
||||
@RequestMapping("/run/status/{job_id}")
|
||||
public ResponseEntity getResult(@PathVariable("job_id") long jobId) {
|
||||
var result = runnerService.Status(jobId);
|
||||
return ResponseEntity.ok().body(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package ru.oa2.edu.api.application.controller;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import ru.oa2.edu.api.application.services.RepositoryService;
|
||||
import ru.oa2.edu.api.domain.theme.ThemeDTO;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/theme")
|
||||
public class ThemeController {
|
||||
|
||||
private final RepositoryService repositoryService;
|
||||
|
||||
public ThemeController(RepositoryService repositoryService) {
|
||||
this.repositoryService = repositoryService;
|
||||
}
|
||||
|
||||
@GetMapping("{id}")
|
||||
@PreAuthorize("hasAnyRole('USER', 'ADMIN', 'CURATOR')")
|
||||
public ThemeDTO getTheme(@PathVariable("id") Long id) {
|
||||
return repositoryService.getTheme(id);
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyRole('CURATOR', 'ADMIN')")
|
||||
public ResponseEntity createTheme(@RequestBody ThemeDTO themeDTO) {
|
||||
repositoryService.createTheme(themeDTO);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PutMapping("{id}")
|
||||
@PreAuthorize("hasAnyRole('CURATOR', 'ADMIN')")
|
||||
public ThemeDTO updateTheme(@PathVariable("id") Long id, @RequestBody ThemeDTO themeDTO) {
|
||||
return repositoryService.updateTheme(id, themeDTO);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package ru.oa2.edu.api.application.controller;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import ru.oa2.edu.api.application.services.RepositoryService;
|
||||
import ru.oa2.edu.api.domain.user.UserInfo;
|
||||
import ru.oa2.edu.api.domain.user.UserRepository;
|
||||
import ru.oa2.edu.api.domain.user.UserStatistic;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/user")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
RepositoryService repositoryService;
|
||||
|
||||
@Autowired
|
||||
UserRepository userRepository;
|
||||
|
||||
@GetMapping("/current")
|
||||
public UserInfo getCurrentUser(Authentication authentication) {
|
||||
var jwtAuth = ((JwtAuthenticationToken) authentication).getTokenAttributes();
|
||||
|
||||
var user = userRepository.findByInternalId(authentication.getName());
|
||||
return new UserInfo(
|
||||
authentication.getName(),
|
||||
jwtAuth.get("family_name").toString(),
|
||||
jwtAuth.get("given_name").toString(),
|
||||
jwtAuth.get("email").toString(),
|
||||
repositoryService.getUserStatistic(user.getId())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.application.database;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.oa2.edu.api.domain.job.Job;
|
||||
import ru.oa2.edu.api.domain.job.JobRepository;
|
||||
|
||||
@Repository
|
||||
public interface JobJpaRepository extends JpaRepository<Job, Long>, JobRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.application.database;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.oa2.edu.api.domain.task.Task;
|
||||
import ru.oa2.edu.api.domain.task.TaskRepository;
|
||||
|
||||
@Repository
|
||||
public interface TaskJpaRepository extends JpaRepository<Task, Long>, TaskRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.application.database;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.oa2.edu.api.domain.theme.Theme;
|
||||
import ru.oa2.edu.api.domain.theme.ThemeRepository;
|
||||
|
||||
@Repository
|
||||
public interface ThemeJpaRepository extends JpaRepository<Theme, Long>, ThemeRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.application.database;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import ru.oa2.edu.api.domain.user.User;
|
||||
import ru.oa2.edu.api.domain.user.UserRepository;
|
||||
|
||||
@Repository
|
||||
public interface UserJpaRepository extends JpaRepository<User, Long>, UserRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.oa2.edu.api.application.database;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import ru.oa2.edu.api.domain.user.UserProgress;
|
||||
import ru.oa2.edu.api.domain.user.UserProgressRepository;
|
||||
|
||||
public interface UserProgressJpaRepository extends JpaRepository<UserProgress, Long>, UserProgressRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package ru.oa2.edu.api.application.services;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import ru.oa2.edu.api.application.database.*;
|
||||
import ru.oa2.edu.api.application.utils.TaskComparator;
|
||||
import ru.oa2.edu.api.application.utils.ThemeComparator;
|
||||
import ru.oa2.edu.api.domain.task.TaskDTO;
|
||||
import ru.oa2.edu.api.domain.theme.Theme;
|
||||
import ru.oa2.edu.api.domain.theme.ThemeDTO;
|
||||
import ru.oa2.edu.api.domain.user.StatJob;
|
||||
import ru.oa2.edu.api.domain.user.UserDataStatistic;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@Transactional
|
||||
public class RepositoryService {
|
||||
|
||||
private final ThemeJpaRepository themeRepository;
|
||||
private final TaskJpaRepository taskRepository;
|
||||
private final UserJpaRepository userRepository;
|
||||
private final UserProgressJpaRepository userProgressRepository;
|
||||
private final JobJpaRepository jobJpaRepository;
|
||||
|
||||
public RepositoryService(ThemeJpaRepository themeRepository,
|
||||
TaskJpaRepository taskRepository,
|
||||
UserJpaRepository userRepository,
|
||||
UserProgressJpaRepository userProgressRepository, JobJpaRepository jobJpaRepository) {
|
||||
this.themeRepository = themeRepository;
|
||||
this.taskRepository = taskRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.userProgressRepository = userProgressRepository;
|
||||
this.jobJpaRepository = jobJpaRepository;
|
||||
}
|
||||
|
||||
public Collection<ThemeDTO> listThemes() {
|
||||
var themes = themeRepository.findAll();
|
||||
var list = new ArrayList<ThemeDTO>();
|
||||
for (var theme : themes) {
|
||||
list.add(
|
||||
ThemeDTO.builder()
|
||||
.id(theme.getId())
|
||||
.name(theme.getName())
|
||||
.active(theme.isActive())
|
||||
.created(theme.getCreated())
|
||||
.updated(theme.getUpdated())
|
||||
.tasks(null)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
list.sort(new ThemeComparator());
|
||||
return list;
|
||||
}
|
||||
|
||||
public ThemeDTO getTheme(Long id) {
|
||||
ThemeDTO themeDTO = null;
|
||||
var result = themeRepository.findById(id);
|
||||
if (result.isPresent()) {
|
||||
var theme = result.get();
|
||||
Collection<TaskDTO> tasks = new ArrayList<>();
|
||||
for (var task : theme.getTasks()) {
|
||||
tasks.add(
|
||||
TaskDTO.builder()
|
||||
.id(task.getId())
|
||||
.name(task.getName())
|
||||
.active(task.isActive())
|
||||
.data(task.getData())
|
||||
.created(task.getCreated())
|
||||
.updated(task.getUpdated())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
tasks = tasks.stream().sorted(new TaskComparator()).toList();
|
||||
themeDTO = ThemeDTO.builder()
|
||||
.id(theme.getId())
|
||||
.name(theme.getName())
|
||||
.active(theme.isActive())
|
||||
.created(theme.getCreated())
|
||||
.updated(theme.getUpdated())
|
||||
.tasks(tasks)
|
||||
.build();
|
||||
}
|
||||
return themeDTO;
|
||||
}
|
||||
|
||||
public TaskDTO getTask(long id) {
|
||||
var task = taskRepository.findById(id);
|
||||
return TaskDTO.builder()
|
||||
.id(task.getId())
|
||||
.name(task.getName())
|
||||
.data(task.getData())
|
||||
.active(task.isActive())
|
||||
.created(task.getCreated())
|
||||
.updated(task.getUpdated())
|
||||
.build();
|
||||
}
|
||||
|
||||
public UserDataStatistic getUserStatistic(long id) {
|
||||
var stats = new HashMap<String, Integer>();
|
||||
var stats2 = new HashMap<String, Integer>();
|
||||
var userData = new UserDataStatistic();
|
||||
var result = jobJpaRepository.findUserStatistic(id);
|
||||
|
||||
for (var job : result) {
|
||||
var date = job.getLastDate().toLocalDateTime().toLocalDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
var isDone = job.isResult() ? 1 : 0;
|
||||
if (stats.containsKey(date)) {
|
||||
stats.put(date, stats.get(date) + 1);
|
||||
stats2.put(date, stats2.get(date) + isDone);
|
||||
} else {
|
||||
stats.put(date, 1);
|
||||
stats2.put(date, isDone);
|
||||
}
|
||||
}
|
||||
for (var stat : stats.entrySet()) {
|
||||
userData.addLabel(stat.getKey());
|
||||
userData.addTotal(stat.getValue());
|
||||
}
|
||||
for (var stat : stats2.entrySet()) {
|
||||
userData.addFinishedCount(stat.getValue());
|
||||
}
|
||||
return userData;
|
||||
}
|
||||
|
||||
public void createTheme(ThemeDTO themeDTO) {
|
||||
var theme = new Theme(themeDTO.getName(), themeDTO.isActive());
|
||||
themeRepository.save(theme);
|
||||
}
|
||||
|
||||
public ThemeDTO updateTheme(Long id, ThemeDTO themeDTO) {
|
||||
// TODO
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.application.services;
|
||||
|
||||
import ru.oa2.edu.api.domain.dto.Job;
|
||||
import ru.oa2.edu.api.domain.dto.JobResult;
|
||||
|
||||
public interface RunnerService {
|
||||
|
||||
long Job(String topicName, Job job);
|
||||
JobResult Status(long jobId);
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
package ru.oa2.edu.api.application.services;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.kafka.annotation.KafkaListener;
|
||||
import org.springframework.kafka.core.KafkaTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ru.oa2.edu.api.application.database.JobJpaRepository;
|
||||
import ru.oa2.edu.api.application.database.TaskJpaRepository;
|
||||
import ru.oa2.edu.api.application.database.ThemeJpaRepository;
|
||||
import ru.oa2.edu.api.application.database.UserJpaRepository;
|
||||
import ru.oa2.edu.api.domain.dto.DataCase;
|
||||
import ru.oa2.edu.api.domain.dto.JobResult;
|
||||
import ru.oa2.edu.api.domain.dto.JobStatus;
|
||||
import ru.oa2.edu.api.domain.job.Job;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
import static ru.oa2.edu.api.application.utils.MappingTypes.marshalRequestData;
|
||||
|
||||
@Log4j2
|
||||
@Service
|
||||
@Transactional
|
||||
public class RunnerServiceImpl implements RunnerService {
|
||||
|
||||
private final KafkaTemplate<String, String> kafkaTemplate;
|
||||
private final UserJpaRepository userRepository;
|
||||
private final TaskJpaRepository taskRepository;
|
||||
private final JobJpaRepository jobRepository;
|
||||
private final ThemeJpaRepository themeRepository;
|
||||
|
||||
public RunnerServiceImpl(KafkaTemplate<String, String> kafkaTemplate,
|
||||
UserJpaRepository userRepository,
|
||||
TaskJpaRepository taskRepository,
|
||||
JobJpaRepository jobRepository,
|
||||
ThemeJpaRepository themeRepository) {
|
||||
this.kafkaTemplate = kafkaTemplate;
|
||||
this.userRepository = userRepository;
|
||||
this.taskRepository = taskRepository;
|
||||
this.jobRepository = jobRepository;
|
||||
this.themeRepository = themeRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long Job(String topicName, ru.oa2.edu.api.domain.dto.Job jobDTO) {
|
||||
var mapperObject = new ObjectMapper();
|
||||
var user = userRepository.findById(jobDTO.getUserId());
|
||||
var task = taskRepository.findById(jobDTO.getTaskId());
|
||||
|
||||
String requestDataString = null;
|
||||
try {
|
||||
requestDataString = marshalRequestData(jobDTO.getRequestData());
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
}
|
||||
var job = new Job(user, task, JobStatus.RUNNING, requestDataString);
|
||||
jobRepository.save(job);
|
||||
|
||||
var uuid = UUID.randomUUID();
|
||||
var requestData = jobDTO.getRequestData();
|
||||
var data = task.getData();
|
||||
DataCase dataCase = null;
|
||||
try {
|
||||
dataCase = mapperObject.readValue(data, DataCase.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
//TODO
|
||||
}
|
||||
requestData.setContextPath(dataCase.getRequestData().getContextPath());
|
||||
var jobResult = new ru.oa2.edu.api.domain.dto.Job(
|
||||
job.getId(),
|
||||
job.getUser().getId(),
|
||||
job.getTask().getId(),
|
||||
jobDTO.getEngineType(),
|
||||
requestData,
|
||||
dataCase.getTestCases()
|
||||
);
|
||||
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
|
||||
String json = null;
|
||||
try {
|
||||
json = ow.writeValueAsString(jobResult);
|
||||
} catch (JsonProcessingException e) {
|
||||
// TODO
|
||||
}
|
||||
kafkaTemplate.send(topicName, uuid.toString(), json);
|
||||
// TODO journal message
|
||||
|
||||
return job.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JobResult Status(long jobId) {
|
||||
var res = jobRepository.findById(jobId);
|
||||
if (res.isPresent()) {
|
||||
var job = res.get();
|
||||
if (job.getStatus().equals(JobStatus.RUNNING)) {
|
||||
return null;
|
||||
}
|
||||
return new JobResult(
|
||||
jobId,
|
||||
job.getTask().getId(),
|
||||
job.isResult(),
|
||||
job.getTaskResponse() != null ? Arrays.stream(job.getTaskResponse().split("\n")).toList() : new ArrayList<>(),
|
||||
null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@KafkaListener(topics = "edu_results", groupId = "edu")
|
||||
public void listenGroupFoo(String message) {
|
||||
System.out.println("Received Message in group foo: " + message);
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JobResult jobResult = null;
|
||||
try {
|
||||
jobResult = mapper.readValue(message, JobResult.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error(e);
|
||||
return;
|
||||
}
|
||||
var res = jobRepository.findById(jobResult.getJobId());
|
||||
// TODO
|
||||
var job = res.get();
|
||||
job.updateResult(jobResult.isStatus(), String.join("\n", jobResult.getLog()));
|
||||
jobRepository.save(job);
|
||||
// TODO как аккумулировать статистику (в процессе запроса или при обработке)
|
||||
// TODO journal message
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package ru.oa2.edu.api.application.utils;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import ru.oa2.edu.api.domain.dto.RequestData;
|
||||
|
||||
public class MappingTypes {
|
||||
|
||||
final static ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public static RequestData parseRequestData(Object object) throws Exception {
|
||||
return objectMapper.readValue((JsonParser) object, RequestData.class);
|
||||
}
|
||||
|
||||
public static String marshalRequestData(RequestData requestData) throws Exception {
|
||||
return objectMapper.writeValueAsString(requestData);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.oa2.edu.api.application.utils;
|
||||
|
||||
import ru.oa2.edu.api.domain.task.TaskDTO;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class TaskComparator implements Comparator<TaskDTO> {
|
||||
@Override
|
||||
public int compare(TaskDTO t1, TaskDTO t2) {
|
||||
return Long.compare(t1.getId(), t2.getId());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package ru.oa2.edu.api.application.utils;
|
||||
|
||||
import ru.oa2.edu.api.domain.theme.ThemeDTO;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public class ThemeComparator implements Comparator<ThemeDTO> {
|
||||
@Override
|
||||
public int compare(ThemeDTO t1, ThemeDTO t2) {
|
||||
return Long.compare(t1.getId(), t2.getId());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class DataCase {
|
||||
|
||||
public DataCase() {}
|
||||
|
||||
@JsonProperty("user_id")
|
||||
private long userId;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("task_id")
|
||||
private long taskId;
|
||||
|
||||
@JsonProperty("engine_type")
|
||||
private String engineType;
|
||||
|
||||
@JsonProperty("request_data")
|
||||
private RequestData requestData;
|
||||
|
||||
@JsonProperty("test_cases")
|
||||
private List<TestCase> testCases;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
public enum EngineTypes {
|
||||
DOCKER ("docker"),
|
||||
DOCKER_BUILDX ("docker_buildx"),
|
||||
KUBERNETES ("kubernetes");
|
||||
|
||||
private final String name;
|
||||
private EngineTypes(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public static EngineTypes fromString(String text) {
|
||||
for (EngineTypes t : EngineTypes.values()) {
|
||||
if (t.name.equalsIgnoreCase(text)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class Job {
|
||||
|
||||
@JsonProperty("job_id")
|
||||
private long jobId;
|
||||
|
||||
@JsonProperty("user_id")
|
||||
private long userId;
|
||||
|
||||
@JsonProperty("task_id")
|
||||
private long taskId;
|
||||
|
||||
@JsonProperty("engine_type")
|
||||
private String engineType;
|
||||
|
||||
@JsonProperty("request_data")
|
||||
private RequestData requestData;
|
||||
|
||||
@JsonProperty("test_cases")
|
||||
private List<TestCase> testCases;
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class JobResult {
|
||||
|
||||
public JobResult() {}
|
||||
|
||||
@JsonProperty("job_id")
|
||||
private long jobId;
|
||||
@JsonProperty("task_id")
|
||||
private long taskId;
|
||||
@JsonProperty("status")
|
||||
private boolean status;
|
||||
|
||||
@JsonProperty("log")
|
||||
private List<String> log;
|
||||
|
||||
@JsonProperty("result")
|
||||
private String result;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
public enum JobStatus {
|
||||
RUNNING,
|
||||
FINISHED,
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class RequestData {
|
||||
|
||||
public RequestData() {}
|
||||
|
||||
@JsonProperty("main")
|
||||
private String main;
|
||||
@JsonProperty("context_path")
|
||||
private String contextPath;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class Run {
|
||||
private long id;
|
||||
private long userId;
|
||||
private long taskId;
|
||||
private JobStatus status;
|
||||
private Object taskRequest;
|
||||
private String taskResponse;
|
||||
private boolean result;
|
||||
private Timestamp lastDate;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class TestCase {
|
||||
|
||||
public TestCase() {}
|
||||
|
||||
@JsonProperty("case_id")
|
||||
private long caseId;
|
||||
@JsonProperty("case_type")
|
||||
private String caseType;
|
||||
@JsonProperty("context")
|
||||
private Object Context;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ru.oa2.edu.api.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class UserProgress {
|
||||
|
||||
public UserProgress() {}
|
||||
|
||||
@JsonProperty("id")
|
||||
private Long id;
|
||||
|
||||
@JsonProperty("user_id")
|
||||
private Long userId;
|
||||
@JsonProperty("theme_id")
|
||||
private Long themeId;
|
||||
@JsonProperty("task_id")
|
||||
private Long taskId;
|
||||
@JsonProperty("count")
|
||||
private Long count;
|
||||
@JsonProperty("done")
|
||||
private boolean done;
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package ru.oa2.edu.api.domain.job;
|
||||
|
||||
import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
import ru.oa2.edu.api.domain.dto.JobStatus;
|
||||
import ru.oa2.edu.api.domain.dto.RequestData;
|
||||
import ru.oa2.edu.api.domain.task.Task;
|
||||
import ru.oa2.edu.api.domain.user.User;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import static ru.oa2.edu.api.application.utils.MappingTypes.parseRequestData;
|
||||
|
||||
|
||||
public class Job {
|
||||
|
||||
|
||||
private long id;
|
||||
private User user;
|
||||
private Task task;
|
||||
private JobStatus status;
|
||||
private String taskRequest;
|
||||
|
||||
private String taskResponse;
|
||||
private boolean result;
|
||||
private Timestamp lastDate;
|
||||
|
||||
public Job() {
|
||||
|
||||
}
|
||||
|
||||
public Job(User user, Task task, JobStatus status, String taskRequest) {
|
||||
this.user = user;
|
||||
this.task = task;
|
||||
this.status = status;
|
||||
this.taskRequest = taskRequest;
|
||||
this.lastDate = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public void updateResult(boolean result, String taskResponse) {
|
||||
this.result = result;
|
||||
this.status = JobStatus.FINISHED;
|
||||
this.taskResponse = taskResponse;
|
||||
this.lastDate = new Timestamp(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Task getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
public void setTask(Task task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public JobStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(JobStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getTaskRequest() {
|
||||
return taskRequest;
|
||||
}
|
||||
|
||||
public void setTaskRequest(String taskRequest) {
|
||||
this.taskRequest = taskRequest;
|
||||
}
|
||||
|
||||
public String getTaskResponse() {
|
||||
return taskResponse;
|
||||
}
|
||||
|
||||
public void setTaskResponse(String taskResponse) {
|
||||
this.taskResponse = taskResponse;
|
||||
}
|
||||
|
||||
public boolean isResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void setResult(boolean result) {
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public Timestamp getLastDate() {
|
||||
return lastDate;
|
||||
}
|
||||
|
||||
public void setLastDate(Timestamp lastDate) {
|
||||
this.lastDate = lastDate;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package ru.oa2.edu.api.domain.job;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface JobRepository {
|
||||
|
||||
List<Job> findUserStatistic(long userId);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package ru.oa2.edu.api.domain.job;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class JobRepositoryImpl implements JobRepository {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public List<Job> findUserStatistic(long userId) {
|
||||
var query = entityManager.createQuery("from Job where user.Id = :userId", Job.class);
|
||||
query.setParameter("userId", userId);
|
||||
return query.getResultList();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package ru.oa2.edu.api.domain.task;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
public class Task {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String data;
|
||||
private boolean active;
|
||||
private LocalDateTime created;
|
||||
private LocalDateTime updated;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(LocalDateTime created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void setUpdated(LocalDateTime updated) {
|
||||
this.updated = updated;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package ru.oa2.edu.api.domain.task;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class TaskDTO {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String data;
|
||||
private boolean active;
|
||||
private LocalDateTime created;
|
||||
private LocalDateTime updated;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.domain.task;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TaskRepository {
|
||||
|
||||
Task save(Task task);
|
||||
Task findById(long id);
|
||||
List<Task> findAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package ru.oa2.edu.api.domain.theme;
|
||||
|
||||
|
||||
import ru.oa2.edu.api.domain.task.Task;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
|
||||
public class Theme {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private boolean active;
|
||||
private LocalDateTime created;
|
||||
private LocalDateTime updated;
|
||||
private Set<Task> tasks = new HashSet<>();
|
||||
|
||||
public Theme() {}
|
||||
|
||||
public Theme(String name, boolean active) {
|
||||
this.name = name;
|
||||
this.active = active;
|
||||
this.created = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
public void setCreated(LocalDateTime created) {
|
||||
this.created = created;
|
||||
}
|
||||
|
||||
public LocalDateTime getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void setUpdated(LocalDateTime updated) {
|
||||
this.updated = updated;
|
||||
}
|
||||
|
||||
public Set<Task> getTasks() {
|
||||
return tasks;
|
||||
}
|
||||
|
||||
public void setTasks(Set<Task> tasks) {
|
||||
this.tasks = tasks;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package ru.oa2.edu.api.domain.theme;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import ru.oa2.edu.api.domain.task.TaskDTO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class ThemeDTO {
|
||||
|
||||
public ThemeDTO() {}
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private boolean active;
|
||||
private LocalDateTime created;
|
||||
private LocalDateTime updated;
|
||||
private Collection<TaskDTO> tasks;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.oa2.edu.api.domain.theme;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ThemeRepository {
|
||||
|
||||
Theme findById(long id);
|
||||
List<Theme> findAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class StatJob {
|
||||
private long runs;
|
||||
private long finish;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
public class User {
|
||||
private Long id;
|
||||
private String internalId;
|
||||
|
||||
public User() {
|
||||
|
||||
}
|
||||
|
||||
public User(String internalId) {
|
||||
this.internalId = internalId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getInternalId() {
|
||||
return internalId;
|
||||
}
|
||||
|
||||
public void setInternalId(String internalId) {
|
||||
this.internalId = internalId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class UserDTO {
|
||||
private String id;
|
||||
private String internalId;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserDataStatistic {
|
||||
private TreeSet<String> labels;
|
||||
private TreeSet<Integer> finishedCount;
|
||||
private TreeSet<Integer> total;
|
||||
private List<TreeSet<Integer>> series;
|
||||
|
||||
public UserDataStatistic() {
|
||||
labels = new TreeSet<>();
|
||||
total = new TreeSet<>();
|
||||
finishedCount = new TreeSet<>();
|
||||
}
|
||||
|
||||
public void addLabel(String label) {
|
||||
labels.add(label);
|
||||
}
|
||||
|
||||
public void addFinishedCount(int count) {
|
||||
finishedCount.add(count);
|
||||
}
|
||||
|
||||
public void addTotal(int count) {
|
||||
total.add(count);
|
||||
}
|
||||
|
||||
public List<TreeSet<Integer>> getSeries() {
|
||||
return List.of(total, finishedCount);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record UserInfo(String id, String family, String name, String email, UserDataStatistic userStatistic) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import ru.oa2.edu.api.domain.task.Task;
|
||||
import ru.oa2.edu.api.domain.theme.Theme;
|
||||
|
||||
|
||||
public class UserProgress {
|
||||
|
||||
|
||||
private Long id;
|
||||
private User user;
|
||||
private Theme theme;
|
||||
private Task task;
|
||||
private Long count;
|
||||
private boolean done;
|
||||
|
||||
public UserProgress() {}
|
||||
|
||||
public UserProgress(User user, Theme theme, Task task, Long count, boolean done) {
|
||||
this.user = user;
|
||||
this.theme = theme;
|
||||
this.task = task;
|
||||
this.count = count;
|
||||
this.done = done;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public Task getTask() {
|
||||
return task;
|
||||
}
|
||||
|
||||
public void setTask(Task task) {
|
||||
this.task = task;
|
||||
}
|
||||
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Long count) {
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
|
||||
public void setDone(boolean done) {
|
||||
this.done = done;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
public interface UserProgressRepository {
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository {
|
||||
|
||||
User findById(long id);
|
||||
User findByInternalId(String internalId);
|
||||
List<User> findAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package ru.oa2.edu.api.domain.user;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record UserStatistic(Map<String, StatJob> stats) {
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
server:
|
||||
port: 8082
|
||||
|
||||
spring:
|
||||
|
||||
application:
|
||||
name: ApiServer
|
||||
main:
|
||||
banner-mode: off
|
||||
|
||||
datasource:
|
||||
url: jdbc:postgresql://localhost:5432/education_system
|
||||
username: postgres
|
||||
password: postgres
|
||||
|
||||
jpa:
|
||||
profiles:
|
||||
hibernate:
|
||||
show_sql: true
|
||||
format_sql: true
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
mapping-resources:
|
||||
- mappings/job.xml
|
||||
- mappings/task.xml
|
||||
- mappings/theme.xml
|
||||
- mappings/internal_user.xml
|
||||
- mappings/user_progress.xml
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
issuer-uri: http://portal.local:8080/keycloak/realms/edu-portal
|
||||
|
||||
kafka:
|
||||
address: "127.0.0.1:29092"
|
||||
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
hibernate:
|
||||
orm.jdbc.bind: trace
|
||||
SQL: debug
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
ARG LIQUIBASE_VERSION=4.9
|
||||
|
||||
FROM liquibase/liquibase:${LIQUIBASE_VERSION}
|
||||
ADD src/main/resources /liquibase/changelog
|
||||
ENTRYPOINT ["/liquibase/docker-entrypoint.sh", "--changeLogFile=db.root-master.xml"]
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
База данных
|
||||
===========
|
||||
|
||||
|
||||
Создание структуры БД
|
||||
---------------------
|
||||
|
||||
```shell
|
||||
docker run \
|
||||
--network=education-system \
|
||||
--volume $PWD/src/main/resources/db:/liquibase/changelog \
|
||||
liquibase/liquibase:4.9 \
|
||||
--url="jdbc:postgresql://postgres:5432/education_system" \
|
||||
--changelog-file=./changelog/db.root-master.xml \
|
||||
--username=postgres \
|
||||
--password=postgres \
|
||||
update
|
||||
```
|
||||
|
||||
Создание тестовых данных
|
||||
------------------------
|
||||
|
||||
```shell
|
||||
docker run \
|
||||
--network=education-system \
|
||||
--volume $PWD/src/main/resources/demo:/liquibase/changelog \
|
||||
liquibase/liquibase:4.9 \
|
||||
--url="jdbc:postgresql://postgres:5432/education_system" \
|
||||
--changelog-file=./changelog/demo.root-master.xml \
|
||||
--username=postgres \
|
||||
--password=postgres \
|
||||
update
|
||||
```
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240831-1" author="dzyk" logicalFilePath="db.20240831-1-create_theme.xml">
|
||||
<comment>
|
||||
Создание таблицы theme
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Список тем для обучения" tableName="theme">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="NAME" remarks="Наименование темы" type="VARCHAR2(2048)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="ACTIVE" remarks="Статус темы (активна/в разработке)" type="BOOLEAN">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="CREATED" remarks="Дата создания" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="UPDATED" remarks="Дата изменения" type="TIMESTAMP">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="theme_id_pk" tableName="theme"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="theme_seq"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240831-2" author="dzyk" logicalFilePath="db.20240831-2-create_list.xml">
|
||||
<comment>
|
||||
Создание таблицы list
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Список задач для темы" tableName="list">
|
||||
|
||||
<column name="THEME_ID" remarks="Идентификатор темы" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="TASK_ID" remarks="Идентификатор задачи" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240831-3" author="dzyk" logicalFilePath="db.20240831-3-create_task.xml">
|
||||
<comment>
|
||||
Создание таблицы task
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Список задач" tableName="task">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="NAME" remarks="Наименование задачи" type="VARCHAR2(2048)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="DATA" remarks="Данные задачи" type="varchar(2048)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="ACTIVE" remarks="Статус темы (активна/в разработке)" type="BOOLEAN">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="CREATED" remarks="Дата создания" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="UPDATED" remarks="Дата изменения" type="TIMESTAMP">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="task_id_pk" tableName="task"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="task_seq" startValue="1"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240831-4" author="dzyk" logicalFilePath="db.20240831-4-create_user.xml">
|
||||
<comment>
|
||||
Создание таблицы user
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Список пользователей" tableName="internal_user">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="INTERNAL_ID" remarks="Внешний идентификатор пользователя" type="varchar(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="user_id_pk" tableName="internal_user"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="user_seq" startValue="1"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240831-5" author="dzyk" logicalFilePath="db.20240831-5-create_user_progress.xml">
|
||||
<comment>
|
||||
Создание таблицы user_progress
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Прогресс пользователей" tableName="user_progress">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="USER_ID" remarks="Идентификатор пользователя" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="THEME_ID" remarks="ID темы" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="TASK_ID" remarks="ID задачи" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="COUNT" remarks="Количество попыток проверки задачи" type="bigint">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="DONE" remarks="Результат прохождения задачи" type="BOOLEAN">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="user_progress_id_pk" tableName="user_progress"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="user_progress_seq" startValue="1"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240919-6" author="dzyk" logicalFilePath="db.20240919-6-create_run.xml">
|
||||
<comment>
|
||||
Создание таблицы job
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Запускаемые пользователями задачи" tableName="job">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="USER_ID" remarks="Идентификатор пользователя" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="TASK_ID" remarks="Идентификатор задачи" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="STATUS" remarks="Статус задачи" type="VARCHAR2(30)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="TASK_REQUEST" remarks="Решение задачи пользователем" type="varchar(2048)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="TASK_RESPONSE" remarks="Ответ от Engine-компонента" type="varchar(2048)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
|
||||
<column name="RESULT" remarks="Результат выполнения задачи (выполнена правильно/нет)" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="LAST_DATE" remarks="Последнее время обновления" type="timestamp">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="run_id_pk" tableName="job"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="run_seq" startValue="1"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240919-7" author="dzyk" logicalFilePath="db.20240919-7-create_journal.xml">
|
||||
<comment>
|
||||
Создание таблицы journal
|
||||
</comment>
|
||||
|
||||
<createTable remarks="Журнал событий" tableName="journal">
|
||||
|
||||
<column name="ID" remarks="Идентификатор" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="USER_ID" remarks="Идентификатор пользователя" type="bigserial">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="RUN_ID" remarks="Идентификатор запуска задачи" type="bigserial">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
|
||||
<column name="LOG" remarks="Запись в журнале" type="bytea">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
<column name="DATE" remarks="Время события" type="timestamp">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="journal_id_pk" tableName="journal"/>
|
||||
<createSequence incrementBy="1" minValue="1" sequenceName="journal_seq" startValue="1"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<include file="db.20240831-1-create_theme.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240831-2-create_list.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240831-3-create_task.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240831-4-create_internal_user.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240831-5-create_user_progress.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240919-6-create_job.xml" relativeToChangelogFile="true"/>
|
||||
<include file="db.20240919-7-create_journal.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<include file="1.0.0/db.version-master.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240921-1" author="dzyk" logicalFilePath="demo.20240921-1-add_themes.xml">
|
||||
<comment>
|
||||
Добавление Тем изучения
|
||||
</comment>
|
||||
|
||||
<insert tableName="theme">
|
||||
<column name="id" value="1"/>
|
||||
<column name="name" value="Базовые знания Docker"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="theme">
|
||||
<column name="id" value="2"/>
|
||||
<column name="name" value="Базовые знания Kubernetes"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="theme">
|
||||
<column name="id" value="3"/>
|
||||
<column name="name" value="Тестовая секция (DEVELOP)"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240921-2" author="dzyk" logicalFilePath="demo.20240921-2-add_tasks.xml">
|
||||
<comment>
|
||||
Добавление заданий
|
||||
</comment>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="1"/>
|
||||
<column name="name" value="Инструкция FROM"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{
|
||||
"engine_type": "docker",
|
||||
"description": "Вы разработали новый сервис на Java 17. Вам необходимо создать Docker образ с вашим приложением, но ваш руководитель в данной задаче настоял, чтобы размер данного образа не превышал 200Mb. Вам необходимо подобрать такой базовый образ, чтобы уложиться в эти размеры. Дополните приведенный пример.",
|
||||
"request_data": {
|
||||
"main": "FROM ...\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar",
|
||||
"context_path": "./contexts/base/case1/"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"case_id": 1,
|
||||
"case_type": "context",
|
||||
"context": {
|
||||
"Field": "$.Size",
|
||||
"Operator": "<",
|
||||
"Value": "209715200"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="2"/>
|
||||
<column name="name" value="Инструкция RUN"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="3"/>
|
||||
<column name="name" value="Инструкция ADD"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="4"/>
|
||||
<column name="name" value="Инструкция LABEL"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="5"/>
|
||||
<column name="name" value="Инструкция WORKDIR"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="6"/>
|
||||
<column name="name" value="Инструкция ARG"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="7"/>
|
||||
<column name="name" value="Использование multistage формата"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="8"/>
|
||||
<column name="name" value="Создание Pod"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="9"/>
|
||||
<column name="name" value="Создание Service"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="10"/>
|
||||
<column name="name" value="Создание Ingress"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="11"/>
|
||||
<column name="name" value="Отладка описания теста"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="12"/>
|
||||
<column name="name" value="Отображение примера задачи"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="task">
|
||||
<column name="id" value="13"/>
|
||||
<column name="name" value="Проверка отображения тестов"/>
|
||||
<column name="active" value="true"/>
|
||||
<column name="data" value="{}"/>
|
||||
<column name="created" value="2024-09-21 19:00:00"/>
|
||||
<column name="updated" value="2024-09-21 19:00:00"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<changeSet id="20240921-3" author="dzyk" logicalFilePath="demo.20240921-3-add_lists.xml">
|
||||
<comment>
|
||||
Связь тем и заданий
|
||||
</comment>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="1"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="2"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="3"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="4"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="5"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="6"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="7"/>
|
||||
<column name="theme_id" value="1"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="8"/>
|
||||
<column name="theme_id" value="2"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="9"/>
|
||||
<column name="theme_id" value="2"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="10"/>
|
||||
<column name="theme_id" value="2"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="11"/>
|
||||
<column name="theme_id" value="3"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="12"/>
|
||||
<column name="theme_id" value="3"/>
|
||||
</insert>
|
||||
|
||||
<insert tableName="list">
|
||||
<column name="task_id" value="13"/>
|
||||
<column name="theme_id" value="3"/>
|
||||
</insert>
|
||||
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<include file="demo.20240921-1-add_themes.xml" relativeToChangelogFile="true"/>
|
||||
<include file="demo.20240921-2-add_tasks.xml" relativeToChangelogFile="true"/>
|
||||
<include file="demo.20240921-3-add_lists.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
|
||||
|
||||
<include file="1.0.0/demo.version-master.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entity-mappings version="2.2"
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||
http://xmlns.jcp.org/xml/ns/persistence/orm/orm_2_2.xsd">
|
||||
|
||||
<package>ru.oa2.edu.api.domain.theme</package>
|
||||
|
||||
<entity class="ru.oa2.edu.api.domain.user.User">
|
||||
<table name="internal_user"/>
|
||||
|
||||
<attributes>
|
||||
<id name="id">
|
||||
<column name="id"/>
|
||||
<generated-value strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<basic name="internalId">
|
||||
<column name="internal_id"/>
|
||||
</basic>
|
||||
</attributes>
|
||||
</entity>
|
||||
</entity-mappings>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entity-mappings version="2.2"
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||
http://xmlns.jcp.org/xml/ns/persistence/orm/orm_2_2.xsd">
|
||||
|
||||
<package>ru.oa2.edu.api.domain.theme</package>
|
||||
|
||||
<entity class="ru.oa2.edu.api.domain.job.Job">
|
||||
<table name="job"/>
|
||||
|
||||
<attributes>
|
||||
<id name="id">
|
||||
<column name="id"/>
|
||||
<generated-value strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<basic name="status">
|
||||
<column name="status" nullable="false"/>
|
||||
<enumerated>STRING</enumerated>
|
||||
</basic>
|
||||
|
||||
<basic name="taskRequest">
|
||||
<column name="task_request" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="taskResponse">
|
||||
<column name="task_response" nullable="true"/>
|
||||
</basic>
|
||||
|
||||
<basic name="result">
|
||||
<column name="result" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="lastDate">
|
||||
<column name="last_date" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<many-to-one name="user" target-entity="ru.oa2.edu.api.domain.user.User" fetch="LAZY">
|
||||
<join-column name="user_id" referenced-column-name="id" insertable="true" updatable="false"/>
|
||||
</many-to-one>
|
||||
|
||||
<many-to-one name="task" target-entity="ru.oa2.edu.api.domain.task.Task" fetch="LAZY">
|
||||
<join-column name="task_id" referenced-column-name="id" insertable="true" updatable="false"/>
|
||||
</many-to-one>
|
||||
</attributes>
|
||||
</entity>
|
||||
</entity-mappings>
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entity-mappings version="2.2"
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||
http://xmlns.jcp.org/xml/ns/persistence/orm/orm_2_2.xsd">
|
||||
|
||||
<package>ru.oa2.edu.api.domain.theme</package>
|
||||
|
||||
<entity class="ru.oa2.edu.api.domain.task.Task">
|
||||
<table name="task"/>
|
||||
|
||||
<attributes>
|
||||
<id name="id">
|
||||
<column name="id"/>
|
||||
<generated-value strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<basic name="name">
|
||||
<column name="name" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="data">
|
||||
<column name="data" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="active">
|
||||
<column name="active" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="created">
|
||||
<column name="created" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="updated">
|
||||
<column name="updated" nullable="true"/>
|
||||
</basic>
|
||||
</attributes>
|
||||
</entity>
|
||||
</entity-mappings>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entity-mappings version="2.2"
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||
http://xmlns.jcp.org/xml/ns/persistence/orm/orm_2_2.xsd">
|
||||
|
||||
<package>ru.oa2.edu.api.domain.theme</package>
|
||||
|
||||
<entity class="Theme">
|
||||
<table name="theme"/>
|
||||
|
||||
<attributes>
|
||||
<id name="id">
|
||||
<column name="id"/>
|
||||
<generated-value strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<basic name="name">
|
||||
<column name="name" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="active">
|
||||
<column name="active" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="created">
|
||||
<column name="created" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="updated">
|
||||
<column name="updated" nullable="true"/>
|
||||
</basic>
|
||||
|
||||
<many-to-many name="tasks" target-entity="ru.oa2.edu.api.domain.task.Task" fetch="LAZY">
|
||||
<join-table name="list">
|
||||
<join-column name="theme_id" column-definition="theme_id" referenced-column-name="id"
|
||||
updatable="true" insertable="true" nullable="false" table="theme"/>
|
||||
<inverse-join-column name="task_id" column-definition="task_id" referenced-column-name="id"
|
||||
updatable="true" insertable="true" nullable="false" table="task"/>
|
||||
</join-table>
|
||||
<cascade>
|
||||
<cascade-persist/>
|
||||
<cascade-refresh/>
|
||||
<cascade-detach/>
|
||||
</cascade>
|
||||
</many-to-many>
|
||||
</attributes>
|
||||
</entity>
|
||||
</entity-mappings>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<entity-mappings version="2.2"
|
||||
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
|
||||
http://xmlns.jcp.org/xml/ns/persistence/orm/orm_2_2.xsd">
|
||||
<package>ru.oa2.edu.api.domain.theme</package>
|
||||
|
||||
<entity class="ru.oa2.edu.api.domain.user.UserProgress">
|
||||
<table name="user_progress"/>
|
||||
|
||||
<attributes>
|
||||
<id name="id">
|
||||
<column name="id"/>
|
||||
<generated-value strategy="IDENTITY"/>
|
||||
</id>
|
||||
|
||||
<basic name="count">
|
||||
<column name="count" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<basic name="done">
|
||||
<column name="done" nullable="false"/>
|
||||
</basic>
|
||||
|
||||
<many-to-one name="user" target-entity="ru.oa2.edu.api.domain.user.User" fetch="LAZY">
|
||||
<join-column name="user_id" referenced-column-name="id" insertable="false" updatable="false"/>
|
||||
</many-to-one>
|
||||
|
||||
<many-to-one name="theme" target-entity="Theme" fetch="LAZY">
|
||||
<join-column name="theme_id" referenced-column-name="id" insertable="false" updatable="false"/>
|
||||
</many-to-one>
|
||||
|
||||
<many-to-one name="task" target-entity="ru.oa2.edu.api.domain.task.Task" fetch="LAZY">
|
||||
<join-column name="task_id" referenced-column-name="id" insertable="false" updatable="false"/>
|
||||
</many-to-one>
|
||||
</attributes>
|
||||
</entity>
|
||||
</entity-mappings>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package ru.oa2.edu.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import ru.oa2.edu.api.application.services.RepositoryService;
|
||||
import ru.oa2.edu.api.domain.theme.ThemeDTO;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@SpringBootTest
|
||||
public class ThemeTest {
|
||||
|
||||
@Autowired
|
||||
private RepositoryService repositoryService;
|
||||
|
||||
@Test
|
||||
public void getListTest() {
|
||||
Collection<ThemeDTO> listThemes = repositoryService.listThemes();
|
||||
// TODO данные для теста
|
||||
System.out.println(listThemes);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>ru.edu.portal</groupId>
|
||||
<artifactId>ok-http-server</artifactId>
|
||||
<version>1.0.0</version>
|
||||
|
||||
<name>Edu Portal Ok Service</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<java.version>21</java.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>ru.edu.portal.OkHttpServer</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>ru.edu.portal.OkHttpServer</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package ru.edu.portal;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
public class OkHttpServer {
|
||||
private final static Logger LOG = Logger.getLogger("NOFRAMEWORKS");
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
final int port = Integer.parseInt(System.getProperty("appPort", "8500"));
|
||||
|
||||
|
||||
final HttpServer server = HttpServer.create(new InetSocketAddress(port), 50);
|
||||
server.createContext("/").setHandler(new Root());
|
||||
server.createContext("/health").setHandler(new Health());
|
||||
|
||||
server.start();
|
||||
}
|
||||
|
||||
private static class Root implements HttpHandler {
|
||||
|
||||
public Root() {}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
exchange.getResponseHeaders().add("Content-Type", "text/html");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
var responseBody = exchange.getResponseBody();
|
||||
responseBody.write("<html><body><h1>OK</h1></body></html>".getBytes());
|
||||
exchange.getResponseBody().close();
|
||||
LOG.info(String.format("[GET] / CODE: 200 - %s", exchange.getRequestHeaders().get("User-Agent")));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Health implements HttpHandler {
|
||||
|
||||
public Health() {}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange exchange) throws IOException {
|
||||
exchange.getResponseHeaders().add("Content-Type", "text/html");
|
||||
exchange.sendResponseHeaders(200, 0);
|
||||
exchange.getResponseBody().close();
|
||||
LOG.info(String.format("[GET] /health CODE: 200 - %s", exchange.getRequestHeaders().get("User-Agent")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.idea
|
||||
*.iml
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"log"
|
||||
)
|
||||
|
||||
type ApiServer struct {
|
||||
router *gin.Engine
|
||||
}
|
||||
|
||||
func NewApiServer() *ApiServer {
|
||||
r := gin.Default()
|
||||
router(r)
|
||||
return &ApiServer{r}
|
||||
}
|
||||
|
||||
func (s *ApiServer) Run(addr string) {
|
||||
err := s.router.Run(addr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func router(router *gin.Engine) {
|
||||
//TODO добавить проверки запуска, возвращать разные коды
|
||||
router.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "UP",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"github.com/docker/docker/api/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type BuildRequest struct {
|
||||
DockerfileName string
|
||||
ImageName string
|
||||
types.BuilderVersion
|
||||
dockerfile string
|
||||
contextPath string
|
||||
}
|
||||
|
||||
func (c *Client) Build(buildRequest BuildRequest) (*string, error) {
|
||||
|
||||
dockerFileTarReader, err := loadContext(buildRequest)
|
||||
|
||||
imageBuildResponse, err := c.cli.ImageBuild(
|
||||
c.ctx,
|
||||
dockerFileTarReader,
|
||||
types.ImageBuildOptions{
|
||||
Tags: []string{buildRequest.ImageName},
|
||||
Context: dockerFileTarReader,
|
||||
Dockerfile: buildRequest.DockerfileName,
|
||||
Version: buildRequest.BuilderVersion,
|
||||
Remove: true})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer imageBuildResponse.Body.Close()
|
||||
|
||||
logBuffer := new(bytes.Buffer)
|
||||
_, err = io.Copy(logBuffer, imageBuildResponse.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log := logBuffer.String()
|
||||
|
||||
return &log, nil
|
||||
}
|
||||
|
||||
func loadContext(buildRequest BuildRequest) (*bytes.Reader, error) {
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
defer tw.Close()
|
||||
|
||||
err := addDockerfile(buildRequest, tw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = addContextFiles(buildRequest.contextPath, tw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes.NewReader(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
func addDockerfile(buildRequest BuildRequest, tarWriter *tar.Writer) error {
|
||||
readDockerFile := []byte(buildRequest.dockerfile)
|
||||
|
||||
tarHeader := &tar.Header{
|
||||
Name: buildRequest.DockerfileName,
|
||||
Size: int64(len(readDockerFile)),
|
||||
}
|
||||
|
||||
err := tarWriter.WriteHeader(tarHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tarWriter.Write(readDockerFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addContextFiles(filePath string, tarWriter *tar.Writer) error {
|
||||
err := filepath.Walk(filePath,
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
err = addFile(path, tarWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addFile(filename string, tarWriter *tar.Writer) error {
|
||||
body, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fileBase := filepath.Base(filename)
|
||||
tarHeader := &tar.Header{
|
||||
Name: fileBase,
|
||||
Size: int64(len(body)),
|
||||
}
|
||||
err = tarWriter.WriteHeader(tarHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tarWriter.Write(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/docker/docker/api/types"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
df = `
|
||||
FROM python:3.8-alpine
|
||||
COPY ./requirements.txt /app/requirements.txt
|
||||
WORKDIR /app
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . /app
|
||||
ENTRYPOINT [ "python" ]
|
||||
|
||||
CMD ["main.py" ]
|
||||
`
|
||||
)
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
client := NewClient()
|
||||
|
||||
result, err := client.Build(
|
||||
BuildRequest{
|
||||
DockerfileName: "Dockerfile",
|
||||
ImageName: fmt.Sprintf("image-%v-%s:latest", 1, time.Now().Format("20060102030405")),
|
||||
BuilderVersion: types.BuilderV1,
|
||||
dockerfile: df,
|
||||
contextPath: "./contexts/case1/",
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
for _, line := range strings.Split(*result, "\n") {
|
||||
if strings.HasPrefix(line, "{") {
|
||||
newLine := LogLine{}
|
||||
err := json.Unmarshal([]byte(line), &newLine)
|
||||
if err != nil {
|
||||
log.Printf("ERR: %s\n", err.Error())
|
||||
log.Printf("Line: \n%s\n", line)
|
||||
}
|
||||
if newLine.Error != nil {
|
||||
t.Errorf("BUILD ERR: %s\n", *newLine.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
name := fmt.Sprintf("image-%v-%s:latest", 1, time.Now().Format("20060102030405"))
|
||||
t.Log(name)
|
||||
t.Fail()
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cli *client.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
cli, err := client.NewClientWithOpts(client.FromEnv)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
client := Client{
|
||||
cli,
|
||||
context.Background(),
|
||||
}
|
||||
return &client
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"log"
|
||||
)
|
||||
|
||||
type StartRequest struct {
|
||||
ImageName string
|
||||
}
|
||||
|
||||
func (c *Client) StartContainer(startRequest StartRequest) (*string, error) {
|
||||
|
||||
containerResponse, err := c.cli.ContainerCreate(
|
||||
c.ctx,
|
||||
&container.Config{
|
||||
Image: startRequest.ImageName,
|
||||
Tty: false},
|
||||
&container.HostConfig{},
|
||||
&network.NetworkingConfig{},
|
||||
nil,
|
||||
"")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Start container ID: %s\n", containerResponse.ID)
|
||||
|
||||
err = c.cli.ContainerStart(c.ctx, containerResponse.ID, container.StartOptions{})
|
||||
if err != nil {
|
||||
log.Printf("ERROR: %s\n", err.Error())
|
||||
}
|
||||
return &containerResponse.ID, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
client := NewClient()
|
||||
|
||||
id, err := client.StartContainer(StartRequest{ImageName: "flask-app:latest"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(id)
|
||||
|
||||
//TODO wait container ???
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
caseTest := TestCase{
|
||||
CaseId: 1,
|
||||
CaseType: "request",
|
||||
Context: ContextRequest{
|
||||
5000,
|
||||
"/hello",
|
||||
"",
|
||||
"GET",
|
||||
200,
|
||||
"Hi, Bro!",
|
||||
},
|
||||
}
|
||||
status, err := client.TestRun(*id, caseTest)
|
||||
if !status {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
err = client.cli.ContainerStop(client.ctx, *id, container.StopOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
err = client.cli.ContainerRemove(client.ctx, *id, container.RemoveOptions{})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextContainer(t *testing.T) {
|
||||
client := NewClient()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
caseTest := TestCase{
|
||||
CaseId: 2,
|
||||
CaseType: "context",
|
||||
Context: ContextTest{
|
||||
"$.Size",
|
||||
"<",
|
||||
"60000000",
|
||||
},
|
||||
}
|
||||
status, err := client.TestRun("flask-app:latest", caseTest)
|
||||
t.Log("status", status, err)
|
||||
if !status {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"job_id": 1,
|
||||
"engine_type": "docker",
|
||||
"request_data": {
|
||||
"main": "FROM eclipse-temurin:17-jre-alpine\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar",
|
||||
"context_path": "./contexts/base/case1/"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"case_id": 1,
|
||||
"case_type": "context",
|
||||
"context": {
|
||||
"Field": "$.Size",
|
||||
"Operator": "<",
|
||||
"Value": "209715200"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"job_id": 1,
|
||||
"engine_type": "docker",
|
||||
"request_data": {
|
||||
"main": "FROM openjdk:17\n\nLABEL org.opencontainers.image.authors=\"Anton Dzyk\"\n\nADD app.jar /opt\n\nCMD exec java -jar /opt/app.jar",
|
||||
"context_path": "./contexts/base/case2/"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"case_id": 1,
|
||||
"case_type": "context",
|
||||
"context": {
|
||||
"Field": "$.Config.Labels",
|
||||
"Operator": "=",
|
||||
"Value": "org.opencontainers.image.authors=Anton Dzyk"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/hello')
|
||||
def hello():
|
||||
return 'Hello!'
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0")
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"job_id": 1,
|
||||
"engine_type": "docker_buildx",
|
||||
"request_data": {
|
||||
"main": "FROM python:3.8-alpine\nADD https://gitflic.ru/project/dzyk/demo-flask.git /app\nWORKDIR /app\nRUN pip install -r requirements.txt\nCOPY . /app\nENTRYPOINT [\"python\"]\nCMD [\"main.py\"]",
|
||||
"context_path": "./contexts/base/case2/"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"case_id": 1,
|
||||
"case_type": "request",
|
||||
"context": {
|
||||
"container_port": 5000,
|
||||
"url_path": "/hello",
|
||||
"request_body": "",
|
||||
"type_request": "GET",
|
||||
"expected_code": 200,
|
||||
"expected_body": "Hello!"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Flask==3.0.0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue