├── .gitignore
├── Procfile
├── README.md
├── SantanderDevWeek2023.ipynb
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
│ └── me
│ │ └── dio
│ │ ├── Application.java
│ │ ├── controller
│ │ ├── UserController.java
│ │ └── exception
│ │ │ └── GlobalExceptionHandler.java
│ │ ├── domain
│ │ ├── model
│ │ │ ├── Account.java
│ │ │ ├── BaseItem.java
│ │ │ ├── Card.java
│ │ │ ├── Feature.java
│ │ │ ├── News.java
│ │ │ └── User.java
│ │ └── repository
│ │ │ └── UserRepository.java
│ │ └── service
│ │ ├── UserService.java
│ │ └── impl
│ │ └── UserServiceImpl.java
└── resources
│ ├── application-dev.yml
│ └── application-prd.yml
└── test
└── java
└── me
└── dio
└── SantanderDevWeek2023ApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 | bin/
17 | !**/src/main/**/bin/
18 | !**/src/test/**/bin/
19 |
20 | ### IntelliJ IDEA ###
21 | .idea
22 | *.iws
23 | *.iml
24 | *.ipr
25 | out/
26 | !**/src/main/**/out/
27 | !**/src/test/**/out/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: java -jar build/libs/santander-dev-week-2023-0.0.1-SNAPSHOT.jar
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Santander Dev Week 2023
2 |
3 | Java RESTful API criada para a Santander Dev Week.
4 |
5 | ## Principais Tecnologias
6 | - **Java 17**: Utilizaremos a versão LTS mais recente do Java para tirar vantagem das últimas inovações que essa linguagem robusta e amplamente utilizada oferece;
7 | - **Spring Boot 3**: Trabalharemos com a mais nova versão do Spring Boot, que maximiza a produtividade do desenvolvedor por meio de sua poderosa premissa de autoconfiguração;
8 | - **Spring Data JPA**: Exploraremos como essa ferramenta pode simplificar nossa camada de acesso aos dados, facilitando a integração com bancos de dados SQL;
9 | - **OpenAPI (Swagger)**: Vamos criar uma documentação de API eficaz e fácil de entender usando a OpenAPI (Swagger), perfeitamente alinhada com a alta produtividade que o Spring Boot oferece;
10 | - **Railway**: facilita o deploy e monitoramento de nossas soluções na nuvem, além de oferecer diversos bancos de dados como serviço e pipelines de CI/CD.
11 |
12 | ## [Link do Figma](https://www.figma.com/file/0ZsjwjsYlYd3timxqMWlbj/SANTANDER---Projeto-Web%2FMobile?type=design&node-id=1421%3A432&mode=design&t=6dPQuerScEQH0zAn-1)
13 |
14 | O Figma foi utilizado para a abstração do domínio desta API, sendo útil na análise e projeto da solução.
15 |
16 | ## Diagrama de Classes (Domínio da API)
17 |
18 | ```mermaid
19 | classDiagram
20 | class User {
21 | -String name
22 | -Account account
23 | -Feature[] features
24 | -Card card
25 | -News[] news
26 | }
27 |
28 | class Account {
29 | -String number
30 | -String agency
31 | -Number balance
32 | -Number limit
33 | }
34 |
35 | class Feature {
36 | -String icon
37 | -String description
38 | }
39 |
40 | class Card {
41 | -String number
42 | -Number limit
43 | }
44 |
45 | class News {
46 | -String icon
47 | -String description
48 | }
49 |
50 | User "1" *-- "1" Account
51 | User "1" *-- "N" Feature
52 | User "1" *-- "1" Card
53 | User "1" *-- "N" News
54 | ```
55 |
56 | ## IMPORTANTE
57 |
58 | Este projeto foi construído com um viés totalmente educacional para a DIO. Por isso, disponibilizamos uma versão mais robusta dele no repositório oficial da DIO:
59 |
60 | ### [digitalinnovationone/santander-dev-week-2023-api](https://github.com/digitalinnovationone/santander-dev-week-2023-api)
61 |
62 | Lá incluímos todas os endpoints de CRUD, além de aplicar boas práticas (uso de DTOs e refinamento na documentação da OpenAPI). Sendo assim, caso queira um desafio/referência mais completa é só acessar 👊🤩
63 |
--------------------------------------------------------------------------------
/SantanderDevWeek2023.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "nbformat": 4,
3 | "nbformat_minor": 0,
4 | "metadata": {
5 | "colab": {
6 | "provenance": [],
7 | "collapsed_sections": [
8 | "kNuP0SDUZMBY"
9 | ],
10 | "authorship_tag": "ABX9TyOsDc/ZTDWGPhbNaEjtoBOl",
11 | "include_colab_link": true
12 | },
13 | "kernelspec": {
14 | "name": "python3",
15 | "display_name": "Python 3"
16 | },
17 | "language_info": {
18 | "name": "python"
19 | }
20 | },
21 | "cells": [
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {
25 | "id": "view-in-github",
26 | "colab_type": "text"
27 | },
28 | "source": [
29 | "
"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "source": [
35 | "# Santander Dev Week 2023 (ETL com Python)"
36 | ],
37 | "metadata": {
38 | "id": "BPJQsTCULaC-"
39 | }
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "source": [
44 | "**Contexto:** Você é um cientista de dados no Santander e recebeu a tarefa de envolver seus clientes de maneira mais personalizada. Seu objetivo é usar o poder da IA Generativa para criar mensagens de marketing personalizadas que serão entregues a cada cliente.\n",
45 | "\n",
46 | "**Condições do Problema:**\n",
47 | "\n",
48 | "1. Você recebeu uma planilha simples, em formato CSV ('SDW2023.csv'), com uma lista de IDs de usuário do banco:\n",
49 | " ```\n",
50 | " UserID\n",
51 | " 1\n",
52 | " 2\n",
53 | " 3\n",
54 | " 4\n",
55 | " 5\n",
56 | " ```\n",
57 | "2. Seu trabalho é consumir o endpoint `GET https://sdw-2023-prd.up.railway.app/users/{id}` (API da Santander Dev Week 2023) para obter os dados de cada cliente.\n",
58 | "3. Depois de obter os dados dos clientes, você vai usar a API do ChatGPT (OpenAI) para gerar uma mensagem de marketing personalizada para cada cliente. Essa mensagem deve enfatizar a importância dos investimentos.\n",
59 | "4. Uma vez que a mensagem para cada cliente esteja pronta, você vai enviar essas informações de volta para a API, atualizando a lista de \"news\" de cada usuário usando o endpoint `PUT https://sdw-2023-prd.up.railway.app/users/{id}`.\n",
60 | "\n"
61 | ],
62 | "metadata": {
63 | "id": "k5fA5OrXt1a3"
64 | }
65 | },
66 | {
67 | "cell_type": "code",
68 | "source": [
69 | "# Utilize sua própria URL se quiser ;)\n",
70 | "# Repositório da API: https://github.com/digitalinnovationone/santander-dev-week-2023-api\n",
71 | "sdw2023_api_url = 'https://sdw-2023-prd.up.railway.app'"
72 | ],
73 | "metadata": {
74 | "id": "FKqLC_CWoYqR"
75 | },
76 | "execution_count": null,
77 | "outputs": []
78 | },
79 | {
80 | "cell_type": "markdown",
81 | "source": [
82 | "## **E**xtract\n",
83 | "\n",
84 | "Extraia a lista de IDs de usuário a partir do arquivo CSV. Para cada ID, faça uma requisição GET para obter os dados do usuário correspondente."
85 | ],
86 | "metadata": {
87 | "id": "9dfI-o7gLRq9"
88 | }
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": null,
93 | "metadata": {
94 | "id": "NYydpX_GLRCB"
95 | },
96 | "outputs": [],
97 | "source": [
98 | "import pandas as pd\n",
99 | "\n",
100 | "df = pd.read_csv('SDW2023.csv')\n",
101 | "user_ids = df['UserID'].tolist()\n",
102 | "print(user_ids)"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "source": [
108 | "import requests\n",
109 | "import json\n",
110 | "\n",
111 | "def get_user(id):\n",
112 | " response = requests.get(f'{sdw2023_api_url}/users/{id}')\n",
113 | " return response.json() if response.status_code == 200 else None\n",
114 | "\n",
115 | "users = [user for id in user_ids if (user := get_user(id)) is not None]\n",
116 | "print(json.dumps(users, indent=2))"
117 | ],
118 | "metadata": {
119 | "id": "F5XOuCZGSTGw",
120 | "colab": {
121 | "base_uri": "https://localhost:8080/"
122 | },
123 | "outputId": "d860ec1c-3180-4d47-f227-edc42ceeb968"
124 | },
125 | "execution_count": null,
126 | "outputs": [
127 | {
128 | "output_type": "stream",
129 | "name": "stdout",
130 | "text": [
131 | "[\n",
132 | " {\n",
133 | " \"id\": 4,\n",
134 | " \"name\": \"Pyterson\",\n",
135 | " \"account\": {\n",
136 | " \"id\": 7,\n",
137 | " \"number\": \"00001-1\",\n",
138 | " \"agency\": \"0001\",\n",
139 | " \"balance\": 0.0,\n",
140 | " \"limit\": 500.0\n",
141 | " },\n",
142 | " \"card\": {\n",
143 | " \"id\": 4,\n",
144 | " \"number\": \"**** **** **** 1111\",\n",
145 | " \"limit\": 1000.0\n",
146 | " },\n",
147 | " \"features\": [],\n",
148 | " \"news\": [\n",
149 | " {\n",
150 | " \"id\": 9,\n",
151 | " \"icon\": \"https://digitalinnovationone.github.io/santander-dev-week-2023-api/icons/credit.svg\",\n",
152 | " \"description\": \"Pyterson, invista hoje para garantir um futuro seguro e pr\\u00f3spero. Seu futuro agradece!\"\n",
153 | " }\n",
154 | " ]\n",
155 | " },\n",
156 | " {\n",
157 | " \"id\": 5,\n",
158 | " \"name\": \"Pip\",\n",
159 | " \"account\": {\n",
160 | " \"id\": 8,\n",
161 | " \"number\": \"00002-2\",\n",
162 | " \"agency\": \"0001\",\n",
163 | " \"balance\": 0.0,\n",
164 | " \"limit\": 500.0\n",
165 | " },\n",
166 | " \"card\": {\n",
167 | " \"id\": 5,\n",
168 | " \"number\": \"**** **** **** 2222\",\n",
169 | " \"limit\": 1000.0\n",
170 | " },\n",
171 | " \"features\": [],\n",
172 | " \"news\": [\n",
173 | " {\n",
174 | " \"id\": 10,\n",
175 | " \"icon\": \"https://digitalinnovationone.github.io/santander-dev-week-2023-api/icons/credit.svg\",\n",
176 | " \"description\": \"Invista hoje para um futuro seguro e est\\u00e1vel, Pip. O seu futuro financeiro depende disso!\"\n",
177 | " }\n",
178 | " ]\n",
179 | " },\n",
180 | " {\n",
181 | " \"id\": 6,\n",
182 | " \"name\": \"Pep\",\n",
183 | " \"account\": {\n",
184 | " \"id\": 9,\n",
185 | " \"number\": \"00003-3\",\n",
186 | " \"agency\": \"0001\",\n",
187 | " \"balance\": 0.0,\n",
188 | " \"limit\": 500.0\n",
189 | " },\n",
190 | " \"card\": {\n",
191 | " \"id\": 6,\n",
192 | " \"number\": \"**** **** **** 3333\",\n",
193 | " \"limit\": 1000.0\n",
194 | " },\n",
195 | " \"features\": [],\n",
196 | " \"news\": [\n",
197 | " {\n",
198 | " \"id\": 11,\n",
199 | " \"icon\": \"https://digitalinnovationone.github.io/santander-dev-week-2023-api/icons/credit.svg\",\n",
200 | " \"description\": \"Oi Pep, investir \\u00e9 a chave para multiplicar seu dinheiro. N\\u00e3o deixe sua grana parada!\"\n",
201 | " }\n",
202 | " ]\n",
203 | " }\n",
204 | "]\n"
205 | ]
206 | }
207 | ]
208 | },
209 | {
210 | "cell_type": "markdown",
211 | "source": [
212 | "## **T**ransform\n",
213 | "\n",
214 | "Utilize a API do OpenAI GPT-4 para gerar uma mensagem de marketing personalizada para cada usuário."
215 | ],
216 | "metadata": {
217 | "id": "cWoqInB4TF1x"
218 | }
219 | },
220 | {
221 | "cell_type": "code",
222 | "source": [
223 | "!pip install openai"
224 | ],
225 | "metadata": {
226 | "id": "O--PCAObTQkK"
227 | },
228 | "execution_count": null,
229 | "outputs": []
230 | },
231 | {
232 | "cell_type": "code",
233 | "source": [
234 | "# Documentação Oficial da API OpenAI: https://platform.openai.com/docs/api-reference/introduction\n",
235 | "# Informações sobre o Período Gratuito: https://help.openai.com/en/articles/4936830\n",
236 | "\n",
237 | "# Para gerar uma API Key:\n",
238 | "# 1. Crie uma conta na OpenAI\n",
239 | "# 2. Acesse a seção \"API Keys\"\n",
240 | "# 3. Clique em \"Create API Key\"\n",
241 | "# Link direto: https://platform.openai.com/account/api-keys\n",
242 | "\n",
243 | "# Substitua o texto TODO por sua API Key da OpenAI, ela será salva como uma variável de ambiente.\n",
244 | "openai_api_key = 'TODO'"
245 | ],
246 | "metadata": {
247 | "id": "sUB1doiDTX3y"
248 | },
249 | "execution_count": null,
250 | "outputs": []
251 | },
252 | {
253 | "cell_type": "code",
254 | "source": [
255 | "import openai\n",
256 | "\n",
257 | "openai.api_key = openai_api_key\n",
258 | "\n",
259 | "def generate_ai_news(user):\n",
260 | " completion = openai.ChatCompletion.create(\n",
261 | " model=\"gpt-4\",\n",
262 | " messages=[\n",
263 | " {\n",
264 | " \"role\": \"system\",\n",
265 | " \"content\": \"Você é um especialista em markting bancário.\"\n",
266 | " },\n",
267 | " {\n",
268 | " \"role\": \"user\",\n",
269 | " \"content\": f\"Crie uma mensagem para {user['name']} sobre a importância dos investimentos (máximo de 100 caracteres)\"\n",
270 | " }\n",
271 | " ]\n",
272 | " )\n",
273 | " return completion.choices[0].message.content.strip('\\\"')\n",
274 | "\n",
275 | "for user in users:\n",
276 | " news = generate_ai_news(user)\n",
277 | " print(news)\n",
278 | " user['news'].append({\n",
279 | " \"icon\": \"https://digitalinnovationone.github.io/santander-dev-week-2023-api/icons/credit.svg\",\n",
280 | " \"description\": news\n",
281 | " })"
282 | ],
283 | "metadata": {
284 | "id": "n1w78kNxTrZY",
285 | "colab": {
286 | "base_uri": "https://localhost:8080/"
287 | },
288 | "outputId": "b3732006-e25c-4c82-e068-ec7ffca14471"
289 | },
290 | "execution_count": null,
291 | "outputs": [
292 | {
293 | "output_type": "stream",
294 | "name": "stdout",
295 | "text": [
296 | "Pyterson, invista para fazer seu dinheiro crescer. Seu futuro financeiro depende disso!\n",
297 | "Pip, investir é o caminho para multiplicar seu dinheiro. Vamos fortalecer seu futuro financeiro!\n",
298 | "Pep, investimentos são a chave para o futuro financeiro. Cresça seu dinheiro, não apenas o guarde!\n"
299 | ]
300 | }
301 | ]
302 | },
303 | {
304 | "cell_type": "markdown",
305 | "source": [
306 | "## **L**oad\n",
307 | "\n",
308 | "Atualize a lista de \"news\" de cada usuário na API com a nova mensagem gerada."
309 | ],
310 | "metadata": {
311 | "id": "kNuP0SDUZMBY"
312 | }
313 | },
314 | {
315 | "cell_type": "code",
316 | "source": [
317 | "def update_user(user):\n",
318 | " response = requests.put(f\"{sdw2023_api_url}/users/{user['id']}\", json=user)\n",
319 | " return True if response.status_code == 200 else False\n",
320 | "\n",
321 | "for user in users:\n",
322 | " success = update_user(user)\n",
323 | " print(f\"User {user['name']} updated? {success}!\")"
324 | ],
325 | "metadata": {
326 | "id": "YefWfYBoZMN2",
327 | "colab": {
328 | "base_uri": "https://localhost:8080/"
329 | },
330 | "outputId": "b0c06200-e14e-4cce-901f-a85ea3eb2830"
331 | },
332 | "execution_count": null,
333 | "outputs": [
334 | {
335 | "output_type": "stream",
336 | "name": "stdout",
337 | "text": [
338 | "User Pyterson updated? True!\n",
339 | "User Pip updated? True!\n",
340 | "User Pep updated? True!\n"
341 | ]
342 | }
343 | ]
344 | }
345 | ]
346 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'java'
3 | id 'org.springframework.boot' version '3.1.2'
4 | id 'io.spring.dependency-management' version '1.1.2'
5 | }
6 |
7 | group = 'me.dio'
8 | version = '0.0.1-SNAPSHOT'
9 |
10 | java {
11 | sourceCompatibility = '17'
12 | }
13 |
14 | repositories {
15 | mavenCentral()
16 | }
17 |
18 | dependencies {
19 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
20 | implementation 'org.springframework.boot:spring-boot-starter-web'
21 | // OpenAPI (Swagger) https://github.com/springdoc/springdoc-openapi
22 | implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
23 |
24 | runtimeOnly 'com.h2database:h2'
25 | runtimeOnly 'org.postgresql:postgresql'
26 |
27 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
28 | }
29 |
30 | tasks.jar {
31 | manifest {
32 | attributes["Main-Class"] = "me.dio.Application"
33 | }
34 | }
35 |
36 | tasks.named('test') {
37 | useJUnitPlatform()
38 | }
39 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/falvojr/santander-dev-week-2023/095a40d7ccc65b1bd7511b86bec6ed82100a4af6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
4 | networkTimeout=10000
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
87 |
88 | # Use the maximum available, or set MAX_FD != -1 to use that value.
89 | MAX_FD=maximum
90 |
91 | warn () {
92 | echo "$*"
93 | } >&2
94 |
95 | die () {
96 | echo
97 | echo "$*"
98 | echo
99 | exit 1
100 | } >&2
101 |
102 | # OS specific support (must be 'true' or 'false').
103 | cygwin=false
104 | msys=false
105 | darwin=false
106 | nonstop=false
107 | case "$( uname )" in #(
108 | CYGWIN* ) cygwin=true ;; #(
109 | Darwin* ) darwin=true ;; #(
110 | MSYS* | MINGW* ) msys=true ;; #(
111 | NONSTOP* ) nonstop=true ;;
112 | esac
113 |
114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
115 |
116 |
117 | # Determine the Java command to use to start the JVM.
118 | if [ -n "$JAVA_HOME" ] ; then
119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
120 | # IBM's JDK on AIX uses strange locations for the executables
121 | JAVACMD=$JAVA_HOME/jre/sh/java
122 | else
123 | JAVACMD=$JAVA_HOME/bin/java
124 | fi
125 | if [ ! -x "$JAVACMD" ] ; then
126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
127 |
128 | Please set the JAVA_HOME variable in your environment to match the
129 | location of your Java installation."
130 | fi
131 | else
132 | JAVACMD=java
133 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
134 |
135 | Please set the JAVA_HOME variable in your environment to match the
136 | location of your Java installation."
137 | fi
138 |
139 | # Increase the maximum file descriptors if we can.
140 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
141 | case $MAX_FD in #(
142 | max*)
143 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
144 | # shellcheck disable=SC3045
145 | MAX_FD=$( ulimit -H -n ) ||
146 | warn "Could not query maximum file descriptor limit"
147 | esac
148 | case $MAX_FD in #(
149 | '' | soft) :;; #(
150 | *)
151 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
152 | # shellcheck disable=SC3045
153 | ulimit -n "$MAX_FD" ||
154 | warn "Could not set maximum file descriptor limit to $MAX_FD"
155 | esac
156 | fi
157 |
158 | # Collect all arguments for the java command, stacking in reverse order:
159 | # * args from the command line
160 | # * the main class name
161 | # * -classpath
162 | # * -D...appname settings
163 | # * --module-path (only if needed)
164 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
165 |
166 | # For Cygwin or MSYS, switch paths to Windows format before running java
167 | if "$cygwin" || "$msys" ; then
168 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
169 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
170 |
171 | JAVACMD=$( cygpath --unix "$JAVACMD" )
172 |
173 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
174 | for arg do
175 | if
176 | case $arg in #(
177 | -*) false ;; # don't mess with options #(
178 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
179 | [ -e "$t" ] ;; #(
180 | *) false ;;
181 | esac
182 | then
183 | arg=$( cygpath --path --ignore --mixed "$arg" )
184 | fi
185 | # Roll the args list around exactly as many times as the number of
186 | # args, so each arg winds up back in the position where it started, but
187 | # possibly modified.
188 | #
189 | # NB: a `for` loop captures its iteration list before it begins, so
190 | # changing the positional parameters here affects neither the number of
191 | # iterations, nor the values presented in `arg`.
192 | shift # remove old arg
193 | set -- "$@" "$arg" # push replacement arg
194 | done
195 | fi
196 |
197 |
198 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
199 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
200 |
201 | # Collect all arguments for the java command;
202 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
203 | # shell script including quotes and variable substitutions, so put them in
204 | # double quotes to make sure that they get re-expanded; and
205 | # * put everything else in single quotes, so that it's not re-expanded.
206 |
207 | set -- \
208 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
209 | -classpath "$CLASSPATH" \
210 | org.gradle.wrapper.GradleWrapperMain \
211 | "$@"
212 |
213 | # Stop when "xargs" is not available.
214 | if ! command -v xargs >/dev/null 2>&1
215 | then
216 | die "xargs is not available"
217 | fi
218 |
219 | # Use "xargs" to parse quoted args.
220 | #
221 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
222 | #
223 | # In Bash we could simply go:
224 | #
225 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
226 | # set -- "${ARGS[@]}" "$@"
227 | #
228 | # but POSIX shell has neither arrays nor command substitution, so instead we
229 | # post-process each arg (as a line of input to sed) to backslash-escape any
230 | # character that might be a shell metacharacter, then use eval to reverse
231 | # that process (while maintaining the separation between arguments), and wrap
232 | # the whole thing up as a single "set" statement.
233 | #
234 | # This will of course break if any of these variables contains a newline or
235 | # an unmatched quote.
236 | #
237 |
238 | eval "set -- $(
239 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
240 | xargs -n1 |
241 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
242 | tr '\n' ' '
243 | )" '"$@"'
244 |
245 | exec "$JAVACMD" "$@"
246 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'santander-dev-week-2023'
2 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/Application.java:
--------------------------------------------------------------------------------
1 | package me.dio;
2 |
3 | import io.swagger.v3.oas.annotations.OpenAPIDefinition;
4 | import io.swagger.v3.oas.annotations.servers.Server;
5 | import org.springframework.boot.SpringApplication;
6 | import org.springframework.boot.autoconfigure.SpringBootApplication;
7 |
8 | @OpenAPIDefinition(servers = { @Server(url = "/", description = "Default Server URL")})
9 | @SpringBootApplication
10 | public class Application {
11 |
12 | public static void main(String[] args) {
13 | SpringApplication.run(Application.class, args);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/controller/UserController.java:
--------------------------------------------------------------------------------
1 | package me.dio.controller;
2 |
3 | import me.dio.domain.model.User;
4 | import me.dio.service.UserService;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.web.bind.annotation.*;
7 | import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
8 |
9 | import java.net.URI;
10 |
11 | @RestController
12 | @RequestMapping("/users")
13 | public class UserController {
14 |
15 | private final UserService userService;
16 |
17 | public UserController(UserService userService) {
18 | this.userService = userService;
19 | }
20 |
21 | @GetMapping("/{id}")
22 | public ResponseEntity findById(@PathVariable Long id) {
23 | var user = userService.findById(id);
24 | return ResponseEntity.ok(user);
25 | }
26 |
27 | @PostMapping
28 | public ResponseEntity create(@RequestBody User userToCreate) {
29 | var userCreated = userService.create(userToCreate);
30 | URI location = ServletUriComponentsBuilder.fromCurrentRequest()
31 | .path("/{id}")
32 | .buildAndExpand(userCreated.getId())
33 | .toUri();
34 | return ResponseEntity.created(location).body(userCreated);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/controller/exception/GlobalExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package me.dio.controller.exception;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.ExceptionHandler;
8 | import org.springframework.web.bind.annotation.RestControllerAdvice;
9 |
10 | import java.util.NoSuchElementException;
11 |
12 | @RestControllerAdvice
13 | public class GlobalExceptionHandler {
14 |
15 | private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
16 |
17 | @ExceptionHandler(IllegalArgumentException.class)
18 | public ResponseEntity handleBusinessException(IllegalArgumentException businessException) {
19 | return new ResponseEntity<>(businessException.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
20 | }
21 |
22 | @ExceptionHandler(NoSuchElementException.class)
23 | public ResponseEntity handleNotFoundException(NoSuchElementException notFoundException) {
24 | return new ResponseEntity<>("Resource ID not found.", HttpStatus.NOT_FOUND);
25 | }
26 |
27 | @ExceptionHandler(Throwable.class)
28 | public ResponseEntity handleUnexpectedException(Throwable unexpectedException) {
29 | var message = "Unexpected server error, see the logs.";
30 | logger.error(message, unexpectedException);
31 | return new ResponseEntity<>(message, HttpStatus.INTERNAL_SERVER_ERROR);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/Account.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.*;
4 |
5 | import java.math.BigDecimal;
6 |
7 | @Entity(name = "tb_account")
8 | public class Account {
9 |
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.IDENTITY)
12 | private Long id;
13 |
14 | @Column(unique = true)
15 | private String number;
16 |
17 | private String agency;
18 |
19 | @Column(precision = 13, scale = 2)
20 | private BigDecimal balance;
21 |
22 | @Column(name = "additional_limit", precision = 13, scale = 2)
23 | private BigDecimal limit;
24 |
25 | public Long getId() {
26 | return id;
27 | }
28 |
29 | public void setId(Long id) {
30 | this.id = id;
31 | }
32 |
33 | public String getNumber() {
34 | return number;
35 | }
36 |
37 | public void setNumber(String number) {
38 | this.number = number;
39 | }
40 |
41 | public String getAgency() {
42 | return agency;
43 | }
44 |
45 | public void setAgency(String agency) {
46 | this.agency = agency;
47 | }
48 |
49 | public BigDecimal getBalance() {
50 | return balance;
51 | }
52 |
53 | public void setBalance(BigDecimal balance) {
54 | this.balance = balance;
55 | }
56 |
57 | public BigDecimal getLimit() {
58 | return limit;
59 | }
60 |
61 | public void setLimit(BigDecimal limit) {
62 | this.limit = limit;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/BaseItem.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.GeneratedValue;
4 | import jakarta.persistence.GenerationType;
5 | import jakarta.persistence.Id;
6 | import jakarta.persistence.MappedSuperclass;
7 |
8 | @MappedSuperclass
9 | public abstract class BaseItem {
10 |
11 | @Id
12 | @GeneratedValue(strategy = GenerationType.IDENTITY)
13 | private Long id;
14 |
15 | private String icon;
16 |
17 | private String description;
18 |
19 | public Long getId() {
20 | return id;
21 | }
22 |
23 | public void setId(Long id) {
24 | this.id = id;
25 | }
26 |
27 | public String getIcon() {
28 | return icon;
29 | }
30 |
31 | public void setIcon(String icon) {
32 | this.icon = icon;
33 | }
34 |
35 | public String getDescription() {
36 | return description;
37 | }
38 |
39 | public void setDescription(String description) {
40 | this.description = description;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/Card.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.*;
4 |
5 | import java.math.BigDecimal;
6 |
7 | @Entity(name = "tb_card")
8 | public class Card {
9 |
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.IDENTITY)
12 | private Long id;
13 |
14 | @Column(unique = true)
15 | private String number;
16 |
17 | @Column(name = "available_limit", precision = 13, scale = 2)
18 | private BigDecimal limit;
19 |
20 | public Long getId() {
21 | return id;
22 | }
23 |
24 | public void setId(Long id) {
25 | this.id = id;
26 | }
27 |
28 | public String getNumber() {
29 | return number;
30 | }
31 |
32 | public void setNumber(String number) {
33 | this.number = number;
34 | }
35 |
36 | public BigDecimal getLimit() {
37 | return limit;
38 | }
39 |
40 | public void setLimit(BigDecimal limit) {
41 | this.limit = limit;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/Feature.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.Entity;
4 |
5 | @Entity(name = "tb_feature")
6 | public class Feature extends BaseItem {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/News.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.Entity;
4 |
5 | @Entity(name = "tb_news")
6 | public class News extends BaseItem {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/model/User.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.model;
2 |
3 | import jakarta.persistence.*;
4 |
5 | import java.util.List;
6 |
7 | @Entity(name = "tb_user")
8 | public class User {
9 |
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.IDENTITY)
12 | private Long id;
13 |
14 | private String name;
15 |
16 | @OneToOne(cascade = CascadeType.ALL)
17 | private Account account;
18 |
19 | @OneToOne(cascade = CascadeType.ALL)
20 | private Card card;
21 |
22 | @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
23 | private List features;
24 |
25 | @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
26 | private List news;
27 |
28 | public Long getId() {
29 | return id;
30 | }
31 |
32 | public void setId(Long id) {
33 | this.id = id;
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public void setName(String name) {
41 | this.name = name;
42 | }
43 |
44 | public Account getAccount() {
45 | return account;
46 | }
47 |
48 | public void setAccount(Account account) {
49 | this.account = account;
50 | }
51 |
52 | public Card getCard() {
53 | return card;
54 | }
55 |
56 | public void setCard(Card card) {
57 | this.card = card;
58 | }
59 |
60 | public List getFeatures() {
61 | return features;
62 | }
63 |
64 | public void setFeatures(List features) {
65 | this.features = features;
66 | }
67 |
68 | public List getNews() {
69 | return news;
70 | }
71 |
72 | public void setNews(List news) {
73 | this.news = news;
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/domain/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package me.dio.domain.repository;
2 |
3 | import me.dio.domain.model.User;
4 | import org.springframework.data.jpa.repository.JpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public interface UserRepository extends JpaRepository {
9 |
10 | boolean existsByAccountNumber(String accountNumber);
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/service/UserService.java:
--------------------------------------------------------------------------------
1 | package me.dio.service;
2 |
3 | import me.dio.domain.model.User;
4 |
5 | public interface UserService {
6 |
7 | User findById(Long id);
8 |
9 | User create(User userToCreate);
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/me/dio/service/impl/UserServiceImpl.java:
--------------------------------------------------------------------------------
1 | package me.dio.service.impl;
2 |
3 | import me.dio.domain.model.User;
4 | import me.dio.domain.repository.UserRepository;
5 | import me.dio.service.UserService;
6 | import org.springframework.stereotype.Service;
7 |
8 | import java.util.NoSuchElementException;
9 |
10 | @Service
11 | public class UserServiceImpl implements UserService {
12 |
13 | private final UserRepository userRepository;
14 |
15 | public UserServiceImpl(UserRepository userRepository) {
16 | this.userRepository = userRepository;
17 | }
18 |
19 | @Override
20 | public User findById(Long id) {
21 | return userRepository.findById(id).orElseThrow(NoSuchElementException::new);
22 | }
23 |
24 | @Override
25 | public User create(User userToCreate) {
26 | if (userRepository.existsByAccountNumber(userToCreate.getAccount().getNumber())) {
27 | throw new IllegalArgumentException("This Account number already exists.");
28 | }
29 | return userRepository.save(userToCreate);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/resources/application-dev.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: jdbc:h2:mem:sdw2023
4 | username: sdw2023
5 | password:
6 | jpa:
7 | show-sql: true
8 | open-in-view: false
9 | hibernate:
10 | ddl-auto: create # validate | update | create | create-drop
11 | properties:
12 | hibernate:
13 | format_sql: true
14 | h2:
15 | console:
16 | enabled: true
17 | path: /h2-console
18 | settings:
19 | trace: false
20 | web-allow-others: false
21 |
--------------------------------------------------------------------------------
/src/main/resources/application-prd.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | datasource:
3 | url: jdbc:postgresql://${PGHOST}:${PGPORT}/${PGDATABASE}
4 | username: ${PGUSER}
5 | password: ${PGPASSWORD}
6 | jpa:
7 | open-in-view: false
8 | hibernate:
9 | ddl-auto: validate
--------------------------------------------------------------------------------
/src/test/java/me/dio/SantanderDevWeek2023ApplicationTests.java:
--------------------------------------------------------------------------------
1 | package me.dio;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SantanderDevWeek2023ApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------