How can I add properties to the mutation successful response - graphql

I am new to graphql-spqr so I hope this is an easy question, however I couldn't find a solution for this, even after a long search.
Hint: In my app, I use the code-first/schema-last approach, which I like about the graphql-spqr, so there is no schema.graphqls loaded from a file.
My User.java starts with this
#Table(name = "users")
#Entity
#Setter
#Getter
#EntityListeners(AuditingEntityListener.class)
public class User {
#Id
#GraphQLQuery(name = "id", description = "A user's id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, updatable = false)
public Long id;
#GraphQLQuery(name = "firstName", description = "User's first name")
#Column(name = "first_name")
public String firstName;
#GraphQLQuery(name = "lastName", description = "User's last name")
#Column(name = "last_name")
public String lastName;
#GraphQLQuery(name = "email", description = "User's email")
public String email;
#GraphQLQuery(name = "uuid", description = "User's uuid")
//#Type(type = "char")
public String uuid;
//#Type(type = "char")
#Transient
public Company company;
#Column(name = "company")
public Long companyId;
#Transient
public Role role;
#Column(name = "role")
public Long roleId;
#Column(name = "pw")
public String password;
#GraphQLQuery(name = "terms", description = "User accepted terms")
public Boolean terms;
#Transient
public String token;
#CreatedDate
public Instant created;
public String getUuid() {
return this.uuid;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
public String getEmail() {
return this.email;
}
public String getPassword() {
return this.password;
}
}
A user is created by a mutation:
#GraphQLMutation(name = "createUser")
public User createUser (
#GraphQLArgument(name = "firstName") String firstName,
#GraphQLArgument(name = "lastName") String lastName,
#GraphQLArgument(name = "email") String email,
#GraphQLArgument(name = "password") String password,
#GraphQLArgument(name = "company") String company,
#GraphQLArgument(name = "terms") Boolean terms) throws UserExistsException {
... some business logic
... and finally I use the JpaRepository<User, String> to save the user
return userRepository.save(user);
}
This is the query I am sending to the server
{"operationName":"CreateUser","variables":{"firstName":"Chris","lastName":"Rowing","email":"foo54#bar.com","password":"dada","company":"Test 5","terms":true,"source":"start","invitationId":null},"query":"mutation CreateUser($firstName: String!, $lastName: String!, $email: String!, $password: String!, $terms: Boolean!, $company: String) {\n createUser(\n firstName: $firstName\n lastName: $lastName\n email: $email\n password: $password\n terms: $terms\n company: $company\n ) {\n id\n __typename\n }\n}\n"}
The new user gets saved in the DB, everything works fine, and in my Angular client I listen to the success event, and in the inspector there is the following output
{"data":{"createUser":{"id":4,"__typename":"User"}}}
My question
How can I customize the response? For example I need to respond also a JWT token, and maybe hide the id. I have not found a way to do this up to now and any help would be appreciated! Thanks!

For anyone who is experiencing the same newbie problem: This is how I solved it:
I added the token property to the GraphQL query, removed the id property, and added this to the UserService
// Attach a new field called token to the User GraphQL type
#GraphQLQuery
public String token(#GraphQLContext User user) {
return authService.createToken(user.email, user.uuid);
}
It is possible to add external fields to the response without changing the original User.class by using #GraphQLContext

Related

Spring boot table to show list of pets by owner id

I'm making a small school project Spring Boot web application. Right now I have made CRUD for Owners table in the database, what I'm trying to do next is when I click button "pets" I want to be able to show only those pets that has the same "owner_id". I guess I should receive "owner_id" from the button that was pressed. How can I make that it works the way it should work? Now when I press button "pets" it shows all list of the pets.
Owner class:
#Entity
#Table(name = "owners")
public class Owner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#Column(name = "email", nullable = false)
private String email;
#OneToMany(targetEntity = Pet.class,cascade = CascadeType.ALL)
#JoinColumn(name = "owner_id", referencedColumnName = "id")
private List<Pet> pets;
public Owner() {
}
public Owner(String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
}
Pet class:
#Table(name = "pets")
public class Pet {
#Id
private Long id;
private String name;
private String breed;
private int age;
private double weight;
public Pet() {
}
public Pet(String name, String breed, int age, double weight) {
super();
this.name = name;
this.breed = breed;
this.age = age;
this.weight = weight;
}
}
Controller method for list of pets:
#GetMapping("/owner_pets")
public String getAllPetsByOwnerId(Model model) {
model.addAttribute("pets", petService.getAllPetsByOwnerId());
return "owner_pets";
}
Here is the code written so far but it only shows list of all pets
I saw your service method for PerService. I do not see any ownerId being passed to findByOwnerId method. That might be the reason why you are getting all pets in response. What you should ideally do is
package com.veterinary.Veterinary_system.service;
import java.util.List;
import com.veterinary.Veterinary_system.entity.Pet;
public interface PetService {
//Repository declaration
List < Pet > findByOwnerId(Long ownerId){
return petRepository.findByOwnerId(ownerId);
}
Pet savePet(Pet pet);
}

JPA: How can I read particular fields of an Entity?

I use Spring JPA ( Hibernate ) and have bunch of entities which are mapped onto tables.
When I use an entity to write I need many fields in it (see an example below). But when I read, I wanna sometimes read only particular fields like first/last name. How can I perform it using Spring data JPA ? ( because due to CrudRepository nature it returns the whole entity)
#Entity
#Table(name="PERSON")
#AttributeOverride(name = "id", column = #Column(name = "ID_PERSON"))
public class Person extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name="LAST_NAME", length = 100, nullable = false)
private String lastName;
#Column(name="FIRST_NAME", length = 50, nullable = false)
private String firstName;
#Column(name="MIDDLE_NAME", length = 50)
private String middleName;
#Column(name="BIRTHDAY", nullable = false)
#Temporal(value = TemporalType.DATE)
private Date birthday;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "ID_SEX")
private Sex sex;
public Person() {
super();
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
There are various possibilities.
With Spring Data JPA you can use projection (that's the name when you only select certain fields/columns of an entity/table).
You can return List of Object[] or a DTO or an Interface.
For example with interface it looks like this:
interface NamesOnly {
String getFirstname();
String getLastname();
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
As you can see the return value most not be of type Person.
Please check out the documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I was faced with a similar issue and I resorted to this:
Let's say you have your entity FooEntity related to repository FooRepository
To only get certain fields, let's say firstName and lastName using key I had to create a custom query in the FooRepository
In this manner
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomByKey(#Param("key") BigInteger key);
You also have to ensure that the FooEntity has the constructor accepting the values that you only want to be set or returned in this manner:
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
Please the full code below:
public class FooEntity implements Serializable {
#Id
#Column(name = "key")
private BigInteger key;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "birth_date")
private Date birthDate;
#Column(name = "hash")
private String hash;
public FooEntity(String firstName, String lastName){
// Ensure the constructor is not called with null values
notNull(firstName, "Method called with null parameter (firstName)");
notNull(lastName, "Method called with null parameter (lastName)");
this.firstName = firstName;
this.lastName = lastName;
}
// Getters and Setters
}
public interface FooRepository extends JpaRepository<FooEntity, BigInteger>{
#Query("select new FooEntity(f.firstName, f.lastName) from FooEntity f where f.key = :key")
Optional<FooEntity> findCustomById(#Param("key") BigInteger key); // This one only returns two set fields firstName and LastName and the rest as nulls
Optional<FooEntity> findById(BigInteger key) // This one returns all the fields
}

Null Foreign Key (Springboot, Hibernate, Postman)

I am using Springboot with Hibernate and I would like to save a new “post” using a POST request to my database. One thing that I would like to highlight is that I am using the dependency “spring-boot-starter-data-rest”.
Schema of the database (MySQL):
Class User:
#Entity
#Table(name="user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id", nullable = false)
public int id;
#OneToMany(mappedBy = "user_id_fk")
public Set<Post> posts;
#Column(name="email")
private String email;
#Column(name="username")
private String username;
#Column(name="password")
private String password;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="create_time")
protected Date createTime;
#Column(name="type")
private String accountType;
public User() {
this.createTime = new java.util.Date();
}
public User(String email, String username, String password, String firstName, String lastName, Date createTime, String accountType) {
this.email = email;
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.createTime = createTime;
this.accountType = accountType;
this.createTime = new java.util.Date();
}
public User(int id, String email, String username, String password, String firstName, String lastName, Date createTime, String accountType) {
this.id = id;
this.email = email;
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.createTime = createTime;
this.accountType = accountType;
this.createTime = new java.util.Date();
}
Plus the Getters & Setters & toString().
Class Post:
#Entity
#Table(name="post")
public class Post implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
public int id;
#ManyToOne(optional = false)
#JoinColumn(name = "user_id_fk", nullable = false)
public User user_id_fk;
#Column(name="comment")
private String comment;
#Column(name="likes")
private int likes;
#Column(name="dislike")
private int dislike;
#Column(name="create_time")
protected Date createTime;
public Post() {
this.createTime = new java.util.Date();
}
public Post(String comment, int likes, int dislike, User user_id_fk) {
this.user_id_fk = user_id_fk;
this.comment = comment;
this.likes = likes;
this.dislike = dislike;
this.createTime = new java.util.Date();
}
public Post(int id, User user_id_fk, String comment, int likes, int dislike) {
this.id = id;
this.user_id_fk = user_id_fk;
this.comment = comment;
this.likes = likes;
this.dislike = dislike;
this.createTime = new java.util.Date();
}
Plus the Getters & Setters & toString().
Post request (I'm using Postman to send the request):
{
"comment" : "This is a comment",
"likes" : 123,
"dislike" : 1,
"user_id_fk" :
[
{
"id" : 1
}
]
}
In the request at the "user_id_fk" I tried with [ {"id" : 1 } ] and with { "id" : 1 } but the result was the same.
Issue:
When I am executing exactly the same code from my controller everything works are excepted. Bear in mind that I am using the dependency “spring-boot-starter-data-rest”.
Also, when I am executing the code without the “optional = false” and “nullable = false” is inserting the data into the database but the “user_id_fk” is null :(.
The error that I am getting:
not-null property references a null or transient value : com.citizen.citizen.entity.Post.user_id_fk;
nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.citizen.citizen.entity.Post.user_id_fk]
That means that the foreign key ("user_id_fk") is null but should not be null.
Any help will be greatly appreciated.
I just remove the dependency "spring-boot-starter-data-rest" and I solved the issue by creating my custom rest and everything works. Kisses!
According to this article, you should make user_id_fk nullable and then:
Send POST to create User
Send second POST to create Post
Send PUT to create a relation between the two.
This article states the same.
And the documentation only mentions handling associations via association links.

Springboot: What is the best way to search for a List of an entity using more than one of its characteristics

I am developing a web application in spring-boot, where a user can search for users using a search field. The users being searched (which depends on the value typed into the input field) will be queried according to their username, first name and last-name. This is my UserModel :
#Component
#Entity
#Table(name = "Users")
public class User extends DefaultEntity {
#Column(name = "FirstName")
#NotNull(message = "Enter a FirstName")
private String firstName;
#Column(name = "LastName")
#NotBlank(message = "Enter a LastName")
private String lastName;
#Column(unique = true,name = "UserName")
#NotBlank(message = "Enter a UserName")
private String userName;
#Column(unique = true, name = "Email")
#NotBlank(message = "Please enter an Email address")
#Email(message = "Enter a valid Email")
private String email;
#Column(name = "Password")
#NotBlank(message = "Enter a Password")
private String password;
#Enumerated(EnumType.STRING)
#Column(name = "Gender")
private Gender gender;
#Column(name = "Address")
#NotBlank(message = "Please enter your Home Address")
private String address;
#Column(name = "Country")
#NotBlank(message = "Please enter your Country")
private String country;
#Column(name = "Picture")
private String picture;
#Column(unique = true, name = "PhoneNumber") //make this accept only numbers
private String phoneNumber;
#Column(name = "Bio")
private String bio;
#Enumerated(EnumType.STRING)
#Column(name = "OnlineStatus")
private OnlineStatus onlineStatus;
#Enumerated(EnumType.STRING)
#Column(name = "UserType")
private UserType userType;
#Column(name = "Money")
private double money;
//#MapsId()
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "playerstats")
private PlayerStats playerStats;
//columnDefinition = "tinyint default false"
#Column(name = "locked",columnDefinition = "BOOL default false")
private Boolean locked;
#Transient
private MultipartFile file;
public String getFirstName() {
return firstName;
}
public User setFirstName(String firstName) {
this.firstName = firstName;
return this;
}
public String getLastName() {
return lastName;
}
public User setLastName(String lastName) {
this.lastName = lastName;
return this;
}
public String getUserName() {
return userName;
}
public User setUserName(String userName) {
this.userName = userName;
return this;
}
public String getEmail() {
return email;
}
public User setEmail(String email) {
this.email = email;
return this;
}
public String getPassword() {
return password;
}
public User setPassword(String password) {
this.password = password;
return this;
}
public Enum.Gender getGender() {
return gender;
}
public User setGender(Enum.Gender gender) {
this.gender = gender;
return this;
}
public String getAddress() {
return address;
}
public User setAddress(String address) {
this.address = address;
return this;
}
public String getCountry() {
return country;
}
public User setCountry(String country) {
this.country = country;
return this;
}
public String getPicture() {
return picture;
}
public User setPicture(String picture) {
this.picture = picture;
return this;
}
public String getBio() {
return bio;
}
public User setBio(String bio) {
this.bio = bio;
return this;
}
public Enum.OnlineStatus getOnlineStatus() {
return onlineStatus;
}
public User setOnlineStatus(Enum.OnlineStatus onlineStatus) {
this.onlineStatus = onlineStatus;
return this;
}
public Enum.UserType getUserType() {
return userType;
}
public User setUserType(Enum.UserType userType) {
this.userType = userType;
return this;
}
public double getMoney() {
return money;
}
public User setMoney(double money) {
this.money = money;
return this;
}
public String getPhoneNumber() {
return phoneNumber;
}
public User setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public MultipartFile getFile() {
return file;
}
public User setFile(MultipartFile file) {
this.file = file;
return this;
}
public PlayerStats getPlayerStats() {
return playerStats;
}
public User setPlayerStats(PlayerStats playerStats) {
this.playerStats = playerStats;
return this;
}
public Boolean getLocked() {
return locked;
}
public void setLocked(Boolean locked) {
this.locked = locked;
}
}
this is my method for querying the usermodel in my UserRepository :
#Repository
public interface UserRepository extends JpaRepository<User,Long> {
Page<User> findUsersByUserNameContainingOrFirstNameContainingOrLastNameContaining(String UserName, String FirstName, String LastName, Pageable pageable);
}
My question: Is there a better way or more efficient way to achieve querying the user entity ?
As mentioned in the comments, what you're looking for is a fuzzy search. This is not something you can easily do within a database, but there are separate search engines that you can use:
Apache Solr (platform based on Apache Lucene)
ElasticSearch
Hibernate Search (Hibernate integration with Apache Lucene)
...
When using such solution, you'll have to index your entities into your search engine as well. Spring Data can help you with that since there is also a library for Solr.
First of all you need a new class that represents how your entity will look like in Solr. Be aware that you want to "flatten" everything if you would have nested relations:
#Document
public class UserDocument {
#Id
#Indexed("id")
private String id;
#Indexed("firstName")
private String firstName;
#Indexed("lastName")
private String lastName;
#Indexed("userName")
private String userName;
// ...
}
After that, you can write a repository like you're used to with Spring Data:
public interface UserDocumentRepository extends SolrCrudRepository<UserDocument, String> {
#Query("userName:?0 OR firstName:?0 OR lastName:?0")
List<UserDocument> findAll(String searchTerm);
}
After that, you can do something like this:
public User create(User input) {
// Create user in database
documentRepository.save(new UserDocument(input.getFirstName(), input.getLastName(), input.getUserName());
}
And you can query for fuzzy searches by using the repository as well:
documentRepository.findAll("vickz~3");
This will use the query that I just wrote, and will look first firstnames, lastnames or usernames containing vickz. The ~3 at the end is a special syntax to indicate that the name can be 3 characters different from the one I just used (= edit distance).
However, this will return the UserDocument Solr entities. If you want to get the entities, you'll have to look them up as well, which can be done by their username:
List<String> usernames = documentRepository
.findAll("vickz~3")
.stream()
.map(UserDocument::getUserName)
.collect(Collectors.toList());
repository.findByUsername(usernames); // Look in database for users matching those usernames

Heroku spring-jpa UniqueConstraint

I met a problem: in my course project I use spring-jpa and create UserEntity with two unique fields. On my local machine all works perfectly well (creates unique constraints in db), but on heroku unique constraints doesn't creates.
I use java9 + spring-jpa.
import javax.persistence.*;
#Entity(name = "UserEntity")
#Table(name = "user_entity", uniqueConstraints = {
#UniqueConstraint(columnNames = {"nickname"}, name = "nickname_constraint"),
#UniqueConstraint(columnNames = {"email"}, name = "email_constraint")
})
public class UserEntity {
private Integer id;
private String nickname;
private String email;
private String passwordHash;
private String avatarPath;
private GameResults gameResults;
public UserEntity() {
}
public UserEntity(String nickname, String email, String password) {
this.nickname = nickname;
this.email = email;
this.passwordHash = password;
}
public UserEntity(String nickname, String password) {
this.nickname = nickname;
this.passwordHash = password;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
#Column(name = "nickname")
public String getNickname() {
return this.nickname;
}
#Column(name = "avatar_path")
public String getAvatarPath() {
return avatarPath;
}
#Column(name = "email")
public String getEmail() {
return email;
}
#Column(name = "password_hash")
public String getPasswordHash() {
return passwordHash;
}
#OneToOne(fetch = FetchType.LAZY)
public GameResults getGameResults() {
return gameResults;
}
// setters ommited
}
Thank you jusermar10!
Really, the problem was that i have deployed application with incorrect jpa entity first time. After redeploying fixed version of application there weren't necessary constraints in the postgres. So dropping db and restarting all dynos helped me.

Resources