Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

Solucionado
(ver solução)
22
respostas

[Bug] Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'forEach') at oferta:138:38

Ao clicar em enviar oferta retornar o seguinte error: Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'forEach') at oferta:138:38, vi outras situações parecidas porém não eram o mesmo erro. Insira aqui a descrição dessa imagem para ajudar na acessibilidade Insira aqui a descrição dessa imagem para ajudar na acessibilidade

home.html

`<html>
<head th:replace="~{base :: head}"></head>
<body onload="onLoad()">
    <div th:replace="~{base :: logo}"></div>
    <div class="container" id="ofertas">
        <div th:replace="~{base :: titulo('Faça sua Ofertas')}"></div>

        <div class="card mb-3" v-for="pedido in pedidos">

            <div class="card-header alert alert-dark">{{pedido.nomeProduto}}</div>
            <div class="card-body">

                <div class="row">
                    <div class="col-12 col-sm-8 mb-3">
                        <div>Produto</div>
                        <div> <a v-bind:href="pedido.urlProduto">{{pedido.nomeProduto}}</a>    </div>

                        <div>Descrição</div>
                        <div>
                            <textarea disabled="disabled" class="form-control">{{pedido.descricao}}</textarea>
                        </div>
                        <div class="row mt-3">
                            <div class="col-md-5">
                                Valor: <input v-bind:class="{'is-invalid':pedido.erros.valor !==''}" class="form-control" v-model="pedido.valorNegociado"/>
                                <div v-if="pedido.erros.valor" class="invalid-feedback">
                                    {{pedido.erros.valor}}
                                </div>
                            </div>
                            <div class="col-md-7">
                                Data da Entrega: <input v-bind:class="{'is-invalid':pedido.erros.dataDaEntrega !==''}" class="form-control" v-model="pedido.dataDaEntrega"/>
                                <div v-if="pedido.erros.dataDaEntrega" class="invalid-feedback">
                                    {{pedido.erros.dataDaEntrega}}
                                </div>
                            </div>
                        </div>
                        <div class="mt-2">
                            <label>Comentário</label>
                            <textarea class="form-control" v-model="pedido.comentario"></textarea>
                        </div>
                        <button v-if="pedido.ofertaEnviada" class="btn btn-success mt-2">Oferta Enviada</button>
                        <button v-else v-on:click="enviarOferta(pedido)" class="btn btn-primary mt-2">Enviar Oferta</button>
                    </div>
                    <div class="col-12 col-sm-4">
                        <div>
                            <img class="img-thumbnail" v-bind:src="pedido.urlImagem" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script>

        function onLoad() {
            var app = new Vue(
                    {
                        el : '#ofertas',
                        data : {
                            pedidos : []
                        },
                        mounted () {
                            axios
                                .get('http://localhost:8080/api/pedidos/aguardando')
                                .then(response => {
                                    response.data.forEach(pedido => {
                                        pedido.ofertaEnviada = false;
                                        pedido.erros = {
                                            valor: '',
                                            dataDaEntrega: ''
                                        }
                                    })
                                    this.pedidos = response.data    
                                })
                        },
                        methods: {
                            enviarOferta: function(pedido) {
                                axios
                                .post('http://localhost:8080/api/ofertas', {
                                    pedidoId: pedido.id,
                                    valor: pedido.valorNegociado,
                                    dataDaEntrega: pedido.dataDaEntrega,
                                    comentario: pedido.comentario
                                })
                                .then(response => pedido.ofertaEnviada = true)
                                .catch(error => {
                                    error.response.data.errors.forEach(error => {
                                        pedido.erros[error.field] = error.defaultMessage;
                                    })
                                })
                            }
                        }
                    });
        }
    </script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://unpkg.com/axios@1.1.2/dist/axios.min.js"></script>
</body>
`
22 respostas

RequisicaoNovaOferta.java

package br.com.alura.mvc.mudi.dto;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import br.com.alura.mvc.mudi.model.Oferta;

public class RequisicaoNovaOferta {

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyy");

    private Long pedidoId;

    @Pattern(regexp="^\\d+(\\.\\d+{2})?$")
    @NotNull
    private String valor;

    @Pattern(regexp="^\\d{2}/\\d{2}/\\d{4}$")
    @NotNull
    private String dataDaEntrega;

    private String comentario;

    public Long getPedidoId() {
        return pedidoId;
    }

    public String getValor() {
        return valor;
    }

    public String getDataDaEntrega() {
        return dataDaEntrega;
    }

    public String getComentario() {
        return comentario;
    }

    public void setPedidoId(Long pedidoId) {
        this.pedidoId = pedidoId;
    }

    public void setValor(String valor) {
        this.valor = valor;
    }

    public void setDataDaEntrega(String dataDaEntrega) {
        this.dataDaEntrega = dataDaEntrega;
    }

    public void setComentario(String comentario) {
        this.comentario = comentario;
    }

    public Oferta toOferta() {
        Oferta oferta = new Oferta();
        oferta.setComentario(this.comentario);
        oferta.setDataDaEntrega(LocalDate.parse(this.dataDaEntrega, formatter));
        oferta.setValor(new BigDecimal(this.valor));
        return oferta;
    }

}

Oi Thiago!

O problema na verdade é na requisição, que está devolvendo erro 400 e por isso ocorre o erro do JavaScript.

Ao chamar a url http://localhost:8080/api/ofertas está dando erro 400, então os dados devem estar sendo enviados de maneira incorreta. Verifica no backend se aparece a exception detalhando quais campos causaram o erro 400.

Rodrigo no eclipse aparece o seguinte:

Hibernate: select pedido0_.id as id1_1_, pedido0_.data_da_entrega as data_da_2_1_, pedido0_.descricao as descrica3_1_, pedido0_.nome_produto as nome_pro4_1_, pedido0_.status as status5_1_, pedido0_.url_imagem as url_imag6_1_, pedido0_.url_produto as url_prod7_1_, pedido0_.user_username as user_use9_1_, pedido0_.valor_negociado as valor_ne8_1_ from pedido pedido0_ inner join users user1_ on pedido0_.user_username=user1_.username where user1_.username=? 2023-01-09 20:07:02.683 WARN 15520 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public br.com.alura.mvc.mudi.model.Oferta br.com.alura.mvc.mudi.api.OfertasRest.criaOferta(br.com.alura.mvc.mudi.dto.RequisicaoNovaOferta) with 2 errors: [Field error in object 'requisicaoNovaOferta' on field 'valor': rejected value [null]; codes [NotNull.requisicaoNovaOferta.valor,NotNull.valor,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requisicaoNovaOferta.valor,valor]; arguments []; default message [valor]]; default message [não deve ser nulo]] [Field error in object 'requisicaoNovaOferta' on field 'dataDaEntrega': rejected value [null]; codes [NotNull.requisicaoNovaOferta.dataDaEntrega,NotNull.dataDaEntrega,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [requisicaoNovaOferta.dataDaEntrega,dataDaEntrega]; arguments []; default message [dataDaEntrega]]; default message [não deve ser nulo]] ]

É isso mesmo, está dando erro de validação pois os campos valor e dataDaEntrega estão indo como null do frontend:

[Field error in object 'requisicaoNovaOferta' on field 'valor': rejected value [null]
[Field error in object 'requisicaoNovaOferta' on field 'dataDaEntrega': rejected value [null]

Rodrigo, a questão é que no front não me aparece o retorno dizendo que o campo têm que ser preenchido, estou utilizando a validação com Vue.js, conforme a imagem abaixo: Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Pelo print do console, na sua primeira mensagem, acho que o problema está aqui:

.catch(error => {
    error.response.data.errors.forEach(error => {
        pedido.erros[error.field] = error.defaultMessage;
    })
})

Pela mensagem no console o problema é o forEach que está sendo feito em um objeto undefined. Coloca um console.log para ver como está chegando o json devolvido pelo servidor:

.catch(error => {
    console.log("Retorno recebido: " +error.response.data);

    error.response.data.errors.forEach(error => {
        pedido.erros[error.field] = error.defaultMessage;
    })
})

Retorno recebido: [object Object]

Ah sim, imprime então com console.dir ou usando o JSON.stringify, para sair todas as propriedades do objeto:

console.dir(error.response.data);

//ou:
//console.log(JSON.stringify(error.response.data));

console.dir(error.response.data);

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

console.log(JSON.stringify(error.response.data));

{"timestamp":"2023-01-11T00:57:46.338+00:00","status":400,"error":"Bad Request","path":"/api/ofertas"}

No json devolvido pelo servidor não estão vindo os erros de validação. Posta aqui sua classe Controller e se tiver uma classe @RestControllerAdvice no projeto posta aqui também.

package br.com.alura.mvc.mudi.api;

import java.util.Optional;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import br.com.alura.mvc.mudi.dto.RequisicaoNovaOferta;
import br.com.alura.mvc.mudi.model.Oferta;
import br.com.alura.mvc.mudi.model.Pedido;
import br.com.alura.mvc.mudi.repository.PedidoRepository;

@RestController
@RequestMapping("/api/ofertas")
public class OfertasRest {

    @Autowired
    private PedidoRepository pedidoRepository;

    @PostMapping
    public Oferta criaOferta(@Valid @RequestBody RequisicaoNovaOferta requisicao) {
        Optional<Pedido> pedidoBuscado = pedidoRepository.findById(requisicao.getPedidoId());
        if(!pedidoBuscado.isPresent()) {
            return null;
        }

        Pedido pedido = pedidoBuscado.get();

        Oferta nova = requisicao.toOferta();
        nova.setPedido(pedido);
        pedido.getOfertas().add(nova);
        pedidoRepository.save(pedido);

        return nova;
    }
}

A princípio está certinho. Uma forma de entender melhor o que está sendo retornado é você utilizar alguma aplicação de testes de API, como o Postman ou Insomnia, e disparar manualmente a requisição enviando um json inválido, para avaliar o que é devolvido.

Exemplo da requisição no Insomnia:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

O retorno deveria ser algo assim:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Quando clico em send o Preview volta para a tela de login:

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Ah sim, é por conta do Spring Security. Desabilita ele comentando o código na classe que tem as configurações de segurança.

Qual parte comento:

package br.com.alura.mvc.mudi;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        .authorizeRequests()
        .antMatchers("/home/**")
            .permitAll()
        .anyRequest()
            .authenticated()
        .and()
        .formLogin(form -> form
            .loginPage("/login")
            .defaultSuccessUrl("/usuario/pedido", true)
            .permitAll()
        )
        .logout(logout -> {
            logout.logoutUrl("/logout")
                .logoutSuccessUrl("/home");
        }).csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

//        PARA CRIAR UM USUÁRIO DIRETO NO BANCO DE DADOS
//        UserDetails user =
//                User.builder()
//                    .username("thiago")
//                    .password(encoder.encode("astra05"))
//                    .roles("ADM")
//                    .build();
//        
        auth.jdbcAuthentication()
            .dataSource(dataSource)
            .passwordEncoder(encoder);
    }

}

Pode deixar o método configure assim:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll().and().csrf().disable()
}

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Boa! Aí está o problema, sua api não está devolvendo a lista com os erros do bean validation. Deve ter alguma classe de tratamento de erros no seu projeto que está causando isso.

Procura se tem alguma classe anotada com @RestControllerAdvice e posta o código dela aqui. Pode ser também alguma configuração no application.properties. Posta ele aqui também.

@RestControllerAdvice não lembro de nenhum.

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mudi
spring.datasource.username=root
spring.datasource.password=astra05

spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=true

Rodrigo, descobrir uma situação que não consegui entender, mudei o nome da pasta do projeto para mudi2, eu iria importar o projeto do curso para verificar se iria funcionar, e para minha surpresa funcionou certinho, agora quando voltei para mudi, parou de funcionar, aí voltei para mudi2 e voltou a funcionar.

hehehe :D ta usando o Eclipse? Deve ser alguma treta do workspace. Sugestão é criar outro workspace e importar o projeto nele para ver se resolve.

solução!

Estou usando bem o Eclipse, obrigado pelo suporte, vou marcar como solucionado.