Solucionado (ver solução)
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.