Quando o Maven não colocar as libs dentro do pacote WEB-INF/lib no projeto por algum motivo, nós podemos dizer ao Maven para fazer isso através da seguinte instrução a ser adicionada no pom.xml:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>./WebContent/WEB-INF/lib</outputDirectory>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
Assim estamos especificando como ele deve executar um goal (objetivo) dentro de uma phase (fase) de build. Verifique se no seu projeto o outputDirectory realmente é "./WebContent/WEB-INF/lib".
Espero ter ajudado!
Esse blog traz várias dicas de programação, principalmente voltadas para o mundo Java, mas também fala sobre Front-End, Banco de Dados e muito mais.
terça-feira, 24 de setembro de 2019
quinta-feira, 12 de setembro de 2019
Passando argumentos para a JVM usando a IDE Eclipse e o Tomcat
Nesse artigo vou mostrar como pasar para parametros para JVM como a quantidade mínima e máxima de memória que deve ser alocada e outros parâmetros que sejam necessários.
Na aba Servers do eclipse, já com o Tomcat adicionado, clique duas vezes sobre o servidor desejado, isso irá abrir o overview do servidor, uma janela de "visão geral".
Nessa tela, em General Information você encontra a opção open launch configuration, clicando nela, será aberta a tela de configuração:
Vá para a aba Arguments e passe os argumentos que você precisa no campo VM Arguments:
Pronto, coloque os argumentos que deseja, aplique e clique em ok!
Na aba Servers do eclipse, já com o Tomcat adicionado, clique duas vezes sobre o servidor desejado, isso irá abrir o overview do servidor, uma janela de "visão geral".
Nessa tela, em General Information você encontra a opção open launch configuration, clicando nela, será aberta a tela de configuração:
Vá para a aba Arguments e passe os argumentos que você precisa no campo VM Arguments:
Pronto, coloque os argumentos que deseja, aplique e clique em ok!
domingo, 8 de setembro de 2019
Trabalhando com múltiplas versões do Node e Angular na mesma máquina
Nesse
post eu vou trazer algo bem interessante que aprendi na minha primeira semana
no novo emprego: como trabalhar com mais de uma versão do Node e do Angular na
mesma máquina de forma simples.
Quem costuma
trabalhar em projetos diferentes e por vezes com versões diferentes do Angular,
sabe que este pode exigir versões diferentes do Node, e justamente para
facilitar essa tarefa existem dois carinhas muito interessantes: um é o NVM e o
outro o NPX. Na verdade, existe um projeto chamado NVM (Node Version Manager)
para Linux/Mac e outro projeto homônimo para Windows. Já o NPX vem no próprio NPM.
Basicamente
a tarefa do NVM é manter versões do Node diferentes instaladas na máquina, e ao
dizer para ele qual usar (com o comando nvm use) ele vai preparar as
variáveis de ambiente e disponibilizar a versão de forma transparente.
O uso da
versão para Windows depois de instalado basicamente se resume a:
nvm install 10.16.3
nvm use
10.16.3
Segue o
link para os projetos NVM:
Versão
para Windows:
Versão
para Linux/Mac:
Agora
vamos falar sobre o NPX. Como já mencionei ele vem com o NPM a partir da
versão 5.2.x pelo que pesquisei, e o que ele faz é executar projetos a partir das
libs do próprio projeto, onde estão os binários necessários, sem ter que
instalar nada globalmente. É o package runner do npm. E caso algo que eu
queira executar não exista na máquina, ele baixa e executa, depois apaga o que
foi baixado para não deixar sujeira na máquina. Inclusive ele roda projetos
diretamente a partir do GitHub.
Para
instalar basta digitar:
npm
install -g npx
E como estamos falando de Angular,
para executar os comandos basta colocar o npx na frente e pronto! Lembre-se! A
partir de agora você não precisa mais instalar coisas globais, apenas nas
dependências do seu projeto!
Para rodar um projeto Angular então,
basta de dentro do diretório do projeto executar:
npx ng
serve
Segue o link do projeto com exemplos e
a documentação:
Abraços!
quinta-feira, 1 de agosto de 2019
Como exibir mensagens de erros específicas para Constraints no Java
Nesse post vou mostrar uma forma de pergar um erro mais específico a partir de uma Exception (nesse caso uma PersistenceException mas poderia ser qualquer outra). Estou assumindo que estamos em uma aplicação JSF e tentamos persistir uma informação que já existe no banco de dados e acaba violando uma constraint, então queremos exibir uma mensagem mais precisa ao usuário. Podemos fazer da seguinte forma:
... //declaração da classe e outras propriedades e métodos
private static final String MENSAGEM_PADRAO = "Não conseguimos consultar as informações, favor informar a área de negócio responsável.";
...
try{
... //seu código
}catch ...{ //outros blocos catch
}catch (final PersistenceException e) {
Exception cause = e;
Integer count = 0;
while (cause != null && cause.getMessage() != null
&& cause.getMessage().contains("NOME_CONSTRAINT_BANCO")
|| count == 10)
{
if ( cause != null )
{
cause = (Exception) cause.getCause();
}
count += 1;
}
String mensagem = null;
if ( cause != null && cause.getMessage() != null
&& cause.getMessage().contains("NOME_CONSTRAINT_BANCO") )
{
mensagem = gerarMensagem("Sua mensagem");
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, mensagem, null));
}
mensagem = gerarMensagem(
MENSAGEM_PADRAO);
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, mensagem, null));
}
}catch (final Exception e)
{
...
}
sábado, 27 de julho de 2019
Resolvendo problema CORS no Angular
Olá,
hoje vou falar sobre o CORS – Cross-Origin Resource Sharing que significa compartilhamento
de recursos de origem cruzada.
Antes
era muito comum o uso de iFrames ou similares para fazer com que um site
acessasse diretamente conteúdo de outros o que também era válido para aplicações
web mais complexas, o problema é que isso começou a ser explorado por pessoas
mal intencionadas para se passarem por um site ou serviço que não são e roubar
informações ou outras atividades ilícitas. Então os navegadores começaram a “bloquear”
esse tipo de coisa, validando antes se a origem tem acesso ao backend vindo de
um IP diferente ou não configurado em uma digamos “White list”.
Trarar
o CORS é obrigação do backend ou infra, mas é possível e também uma boa prática
tratar no frontend. Para isso criamos um Proxy no nosso frontend. Como
funciona um proxy? Ele recebe a requisição e a repassa com uma nova
configuração de rede.
Vamos criar o nosso proxy, crie
um arquivo chamado proxy.config.js no mesmo diretório em que se encontra
o seu package.json com o seguinte
conteúdo:
{
"/api":
{
"target":
"http://localhost:8080", //endereço do backend
"secure": false
}
}
Agora configure dentro do
seu angular.json o uso do proxy:
...
"defaults":{
"serve":{
"proxyConfig":"./proxy.config.js"
}
...
"defaults":{
"serve":{
"proxyConfig":"./proxy.config.js"
}
...
Fonte:
Para ver todas as opções da configuração de proxy
veja:
https://webpack.js.org/configuration/dev-server/#devserver-proxy
quarta-feira, 17 de julho de 2019
Método que verifica interseção entre períodos
Neste artigo rápido trago um método que verifica se há interseção entre dois períodos de datas. Todo sistema precisa trabalhar com datas e é muito comum precisarmos desse tipo de validação:
public static boolean isIntersecaoEntrePeriodos(final Date aIni, final Date aFim, final Date bIni,
final Date bFim)
{
if ( (aFim == null && bFim == null)
|| (aFim == null && aIni.compareTo(bIni) <= 0)
|| (aFim == null && bFim != null && aIni.compareTo(bFim) <= 0)
|| (bFim == null && bIni.compareTo(aIni) <= 0)
|| (bFim == null && aFim != null && bIni.compareTo(aFim) <= 0)
|| ((aFim != null && bFim != null) &&
((aIni.compareTo(bIni) <= 0 && aFim.compareTo(bFim) >= 0)
|| (aIni.compareTo(bIni) <= 0 && aFim.compareTo(bIni) >= 0)
|| (aIni.compareTo(bIni) >= 0 && aFim.compareTo(bFim) <= 0)
|| (aIni.compareTo(bFim) <= 0 && aFim.compareTo(bFim) >= 0))) )
{
return true;
}
return false;
}
public static boolean isIntersecaoEntrePeriodos(final Date aIni, final Date aFim, final Date bIni,
final Date bFim)
{
if ( (aFim == null && bFim == null)
|| (aFim == null && aIni.compareTo(bIni) <= 0)
|| (aFim == null && bFim != null && aIni.compareTo(bFim) <= 0)
|| (bFim == null && bIni.compareTo(aIni) <= 0)
|| (bFim == null && aFim != null && bIni.compareTo(aFim) <= 0)
|| ((aFim != null && bFim != null) &&
((aIni.compareTo(bIni) <= 0 && aFim.compareTo(bFim) >= 0)
|| (aIni.compareTo(bIni) <= 0 && aFim.compareTo(bIni) >= 0)
|| (aIni.compareTo(bIni) >= 0 && aFim.compareTo(bFim) <= 0)
|| (aIni.compareTo(bFim) <= 0 && aFim.compareTo(bFim) >= 0))) )
{
return true;
}
return false;
}
sábado, 25 de maio de 2019
Usando Hibernate Envers para auditoria
Pra
quem não conhece o Hibernate Envers é o módulo de auditoria e versionamento de
entidades. Neste post vou mostrar como configurar o envers no Spring Boot.
Primeiro
precisamos adicionar a dependência do Envers no POM do nosso projeto:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>5.3.1.Final</version>
</dependency>
Precisamos
também criar uma tabela padrão do Envers chamada “REVINFO” com dois campos, um
ID e um campo pra guardar o timestamp das alterações:
CREATE
TABLE revinfo (
rev integer NOT NULL,
revtstmp bigint,
CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)
Note
que o campo para guardar a chave de revisão por padrão é um integer, para mudar
para bigint é preciso fazer customização do Envers, criando uma entidade que
contenha o campo do tipo Long como abaixo:
@Entity
@RevisionEntity
public class EntityRevisionCustom implements Serializable {
@Id
@GeneratedValue
@RevisionNumber
private Long rev;
@RevisionTimestamp
private Long timestamp;
/* getts and
setts */
}
CREATE TABLE revinfo (
rev bigint NOT NULL,
revtstmp bigint,
CONSTRAINT revinfo_pkey PRIMARY KEY (rev)
)
Outra
coisa que leva a criação dessa classe customizada é poder colocar mais
informações nela, como por exemplo o usuário logado, que é o responsável pela alteração.
Para isto é preciso criar um listener que possa pegar esse usuário e settar na
entidade de forma transparente para o desenvolvedor.
Isso
requer que informemos o Listener na classe customizada:
@Entity
@RevisionEntity(MyRevisionListener.class)
public class EntityRevisionCustom implements Serializable {
...
}
Segue o exemplo de um listener que pega o usuário da sessão e preenche na classe
customizada:
public class MyRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
EnversCustomRevision rev = (EnversCustomRevision) revisionEntity;
rev.setUsuCodUsuario(SessionBean.getCodUsuarioLogado());
}
}
Depois de
adicionar a dependência e criar a tabela revinfo (fazendo as customizações ou
não), podemos usar o Envers na nossa aplicação. Basta anotar cada Entidade que for auditada com a
anotação @Audited do pacote org.hibernate.envers
e criar uma tabela de mesmo nome da
tabela original adicionando o sufixo “_AUD” (ou “_aud”) dependento do seu
padrão, acrescentando dois campos: rev
(do tipo integer se não tiver customizado) e revtype (do tipo smalint).
@Entity
@Table(name = "USU_USUARIO")
@Audited
public class Usuario implements Serializable {
...
}
Caso a
tabela criada para auditoria seja diferente do nome da tabela da entidade + o
sufixo “_AUD”, deve ser informado na classe colocando a anotação @AuditTable, que recebe a propriedade value para informar o nome da tabela a
ser usada.
@AuditTable(value = "USU_AUD")
Pronto,
assim o sistema já está sendo auditado.
segunda-feira, 22 de abril de 2019
Configurando o computador para desenvolver projetos Java Web (JavaEE)
Este artigo é um
guia rápido das ferramentas a serem instaladas para deixar a máquina pronta para trabalhar com
projetos JavaEE, te levando aos links que te ajudarão a instalar o JDK, o Git,
o Maven, o Wildfly, o Eclipse e dentro do eclipse o JBoss Tools.
Para configurar o
JDK 8 em qualquer plataforma, acesse:
Para configurar o
Maven, acesse:
Para instalar o
Git, acesse:
Para baixar o Eclipse
para JavaEE basta pesquisar exatamente por esse termo no Google e abrir o
primeiro link, então procurar a versão Enterprise Java Developers e baixar
segundo a sua plataforma, segue link da ultima versão até a data desse artigo:
Agora vamos
instalar o Wildfly, que é o servidor de aplicações, esse primeiro link é para
download:
Este segundo é o
guia para desenvolvedores, caso nunca tenha trabalhado com o wildfly:
Esta última parte
é para usarmos o wildfly dentro do eclipse para facilitar o desenvolvimento.
Dentro do eclipse, abra o menu Marketplace
que geralmente fica dentro do menu help,
e procure por JBoss Tools, instale a última versão disponível:
Para banco de dados uso o PostgreSQL, que é um banco grátis
e super poderoso:
Agora temos todas as ferramentas para trabalhar com JavaEE.
sexta-feira, 22 de março de 2019
Pegando o RealPath em uma aplicação JEE
As
vezes precisamos do RealPath da nossa aplicação para lermos e/ou escrevermos
arquivos através do Java, e em aplicações JavaEE uma boa forma de fazermos isso
é através de um ServletContextListener, uma vez que este listener escuta quando
sua aplicação inicializa ou finaliza no container web ou servidor de aplicação.
Usando
o ServletContextListener ao invés do FacesContext para pegar o RealPath não
ficamos presos ao JSF por exemplo, ou outra tecnologia, e podemos usar o RealPath
em processos e situações diversas.
Geralmente
tenho uma classe em um pacore br.com.meudominio.utils chamada Constants onde
guardo algumas constantes e variáveis estáticas com algumas configurações do
sistema (embora prefira usar um cadastro de Parâmetros Gerais do Sistema para a
maioria das configurações), e ao pegar o RealPath é nessa classe que seto essa
configuração. Veja o código do listener abaixo:
import javax.servlet.ServletContextEvent;
import
javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebFilter;
import br.com.meudominio.Constants;
@WebListener
public class RealPath implements ServletContextListener{
@Override
public void contextDestroyed( ServletContextEvent sce ) {
}
@Override
public void contextInitialized( ServletContextEvent sce ) {
Constants.REAL_PATH = sce.getServletContext().getRealPath( "/" );
}
}
Na
classe Constants tenho algo como:
public final class Constants
{
...
public static String REAL_PATH = "";
...
}
Pronto,
em qualquer lugar do sistema basta usar Constants.REAL_PATH
para pegar o RealPath da minha aplicação.
terça-feira, 19 de março de 2019
Criando uma biblioteca de componentes com Angular 7, utilizando em projetos externos e publicando no NPM
Frameworks
JavaScript estão sempre mudando, e geralmente são mudanças complexas que nos
obrigam a reaprender como fazemos as coisas. Não foi diferente com a questão da
criação de bibliotecas do Angular, que até a versão 5 era apenas um projeto
comum, o qual empacotávamos no formato NPM e publicávamos. Agora, a partir da
versão 7 em diante o Angular cria uma espécie de workspace, onde é possível
criar vários projetos e bibliotecas, e são essas bibliotecas internas que podemos
exportar e podemos usar em outros projetos, e até publicar no NPM.
Com
isso, muita coisa mudou, porque essa biblioteca interna, (pelo menos por
enquanto, até a versão 7.1.2 do Angular), não contém os arquivos environments.prod.ts e environments.ts para tratarmos a
diferença entre algumas configurações de produção e o ambiente de
desenvolvimento, também não existe a estrutura da pasta assets para colocarmos nossas imagens e o Angular conseguir encontrá-las
de forma fácil.
Essas
dificuldades eu contornei da seguinte forma: as imagens eu coloco em texto
base64 direto na tag <img> e quando preciso pegar uma URL em uma
biblioteca uso JavaScript para obter a URL de forma dinâmica: (window.location.protocol
+ window.location.hostname;).
Outra
mudança que houve foi o arquivo angular-cli.json
que teve seu nome alterado para angular.json
e sua estrutura mudou um pouco, passando a conter múltiplos projetos (como o de
testes e as bibliotecas) pois como
disse, o que criamos com “ng new” a partir da versão 7 do Angular se comporta
como um workspace.
Agora
que já falamos das mudanças e alguns contratempos que tive com elas, vamos a
criação do projeto. Abrindo no terminal a pasta do seu workspace, digite:
$ ng
new teste --create-application=false
Quando indagado sobre a criação do Router
e do CSS escolha as opções padrão.
A opção
--create-aplication usada acima
também é nova, e faz com que o Angular não crie o projeto inicial, bem com a
pasta src, criando apenas o Workspace
Angular por assim dizer. Basicamente isso é útil para criarmos bibliotecas,
pois gera um workspace de mesmo nome da biblioteca. Note que o Angular aceita tanto comandos com camelCase
quanto separados por ífem (-), então poderia ser --createApplication=false.
Aproveite e dê uma olhada nos arquivos
package.json que continua com as
dependências do projeto e angular.json, que está praticamente
vazio aguardando a criação de um projeto ou biblioteca.
Vamos criar a biblioteca, posicione o
terminal na nova pasta criada (teste):
$ cd
..
E crie
a biblioteca com o seguinte comando:
$ ng
g library teste --prefix=lib
Na linha acima, foi criada a
biblioteca “teste” e ao invés de
usarmos o prefixo “app” como de
costume, dissemos para o Angular usar o prefixo “lib”. Fique à vontade para escolher o prefixo que desejar, isso é
importante para evitar conflitos entre a biblioteca e os apps que a usam.
Note que o conteúdo do arquivo angular.json mudou, temos nele a
propriedade que indica a versão do projeto: “version”, a propriedade que indica o pacote raiz que contém os
projetos: “newProjectRoot”, e então
temos a propriedade que indica os nossos projetos: “projects”. Dentro da propriedade “projects” há uma propriedade para cada projeto que criarmos, nesse
caso haverá apenas o projeto “teste”,
e dentro da propriedade “teste” teremos
as propriedades de configuração deste projeto específico, como o tipo do projeto, o prefixo usado, a raiz do
projeto, o source do projeto e
outras configurações pertinentes.
A
estrutura do projeto também foi alterada, agora existe uma pasta “projects” com a pasta “teste” que é o nosso projeto dentro
dela; e dentro da pasta “teste”
temos o diretório “src” que conterá tudo que pertencer a lib propriamente
dita, o arquivo “package.json” da lib que especificará suas
dependências que serão compiladas e posteriormente instaladas junto com ela
quando um cliente usar npm install e
também o arquivo ng-package.json. Os
demais arquivos são de menor importância. É também no arquivo “package.json” da lib que especificamos
o autor, a licença, o repositório, etc.
A lib já foi criada contendo um
componente e um service dentro de src/lib
. Também vemos outro arquivo importante dentro da pasta src: public_api.ts. Nesse arquivo devemos exportar
todos os módulos, componentes e services que criarmos, para que possam ser
usados por quem instalar nossa biblioteca.
export * from './lib/teste.service';
export * from './lib/teste.component';
export * from './lib/teste.module';
No
diretório principal, o arquivo “tsconfig.json”
tem as configurações de transpilação e build do projeto. Ele recebe o path
da biblioteca que criamos para podermos usar nesse projeto, enquanto que em
projetos externos ela será instalada com npm
install e o conteúdo ficará em node_modules.
"paths": {
"teste": [
"dist/teste"
],
"teste/*": [
"dist/teste/*"
]
}
Vamos buildar o projeto:
$ ng
build teste
Veja que o projeto é buildado
normalmente. Mas a frente vamos ver como usá-lo em outro projeto e também como
publicar no npm. Agora vamos ver como criar novos componentes para a
biblioteca. Digite o comando abaixo:
$ ng
g c meu-componente --project=teste
Veja
que a única coisa que
mudou foi que especificamos em qual projeto o componente seria gerado pelo
AngularCli. O novo componente deve ser declarado no módulo da nossa
biblioteca e também no arquivo de entrada, o public_api.ts.
Com
isso podemos re-buildar nossa aplicação. Uma novidade desde a versão 6.2 é o
re-build pode ser automático a medida que arquivos forem alterados, para isso
basta acrescentar a opção --watch no momento do build, a partir de então os
demais serão disparados sozinhos.
$ ng
build teste --watch
Agora
vamos ver como usar a biblioteca em um
projeto externo e como publicar no npm.
Para isso
temos que empacotar nossa biblioteca em uma arquivo .tgz, por tanto, vamos criar um script no “package.json” raiz para facilitar este trabalho. Na seção “scripts” adicione o seguinte trecho de
código:
"scripts": {
...
"npm_pack_teste": "cd dist/teste && npm
pack"
}
Agora
podemos usar o seguinte comando para empacotar a biblioteca “teste”:
$ npm
run npm_pack_teste
Isto
criou o arquivo “teste-0.0.1.tgz” dentro
de “dist/teste” com a versão
especificada no arquivo “package.json” da
lib como já dito anteriormente. Podemos inclusive criar um script que faz o build
e o empacotamento de uma só vez:
"scripts": {
...
"npm_pack_teste": "cd dist/teste && npm
pack",
"package_teste":
"ng build teste && npm run npm_pack_teste "
}
E agora
basta executar:
$ npm
run package_teste
Pronto,
temos nossa biblioteca buildada e empacotada para usarmos onde quisermos! Para usarmos em projetos próprios não
precisamos publicar no npm. Isso é bom para empresas que não querem expor suas bibliotecas, pode-se
criar um repositório interno para elas e quando for usar o npm install basta passar
o caminho completo de onde a biblioteca está.
Uma vez
instalada a biblioteca, o uso é normal como qualquer pacote npm instalado na aplicação.
Agora vamos a publicação no npm. Antes
de criar uma biblioteca, se é sua intenção publicá-la no npm é bom checar se o
nome que pretende dar a sua biblioteca já não existe lá, para evitar ter que
mudar depois de criado todo o projeto.
O
arquivo “package.json” deve estar com
as informações de nome, versão, autor, licença, repositório, palavras chaves e
outras além das dependências, scripts, etc... como abaixo:
...
"name": "teste",
"version": "1.0.0",
"license": "MIT",
"author": {
"name": "seu_nome",
"email":
"seu_email",
"url": "seu_site_ou_blog"
},
"bugs": {
"url":"url_issues_git"
},
"homepage":"url_git",
"keywords": [
"angular",
"angular2",
"angular4",
"p-calendar",
"p-calendar-ptbr",
"primeNG",
"datepicker",
"calendar",
"pt-BR"
],
"repository": {
"type":
"git",
"url":
"git+https://endereço_rep_git"
},
...
O npm
usará sua página de README.md com a página inicial da sua biblioteca no
servidor. Como agora estamos tratando de mais de uma biblioteca por
workspace, o README.md deve ser copiado para o destino onde a biblioteca é
buildada. Caso haja um arquivo LICENSE também deve ser colocado lá.
É preciso
criar uma conta caso você não tenha, ou fazer login para poder compartilhar
seus pacotes. A criação da conta é bem simples e eles pedem um username,
password e e-mail. É necessário a confirmação do e-mail antes de publicar algo
caso a conta seja nova. O comando para criar uma conta é:
$
npm adduser
Ao
criar a conta ele já loga no NPM. Para efetuar logins posteriores, os mesmos
dados são pedidos, e o comando é:
$
npm login
Agora
podemos publicar nossa biblioteca, digitando o comando npm publish e passando o
caminho do arquivo .tgz gerado:
$ npm
publish .\dist\teste\teste-0.0.1.tgz
Bom, o
artigo ficou um pouco grande porque expliquei algumas coisas com detalhes, mas
espero ter ajudado! Abraços!
Links
de referência:
Assinar:
Postagens (Atom)