vikram-Ajabe -Software Engineer Assignment

This commit is contained in:
Admin 2025-07-01 10:34:40 +05:30
parent f9399b7a9f
commit 2c5cf16284
15 changed files with 507 additions and 62 deletions

View file

@ -0,0 +1,78 @@
package org.springframework.samples.petclinic.owner;
import org.springframework.samples.petclinic.model.BaseEntity;
import jakarta.persistence.*;
@Entity
@Table(name = "pet_attributes")
public class PetAttributes extends BaseEntity {
private static final long serialVersionUID = 1L;
@Column(name = "petId", nullable = false, unique = true)
private Long petId;
@Column(name = "temperament")
private String temperament;
@Column(name = "length")
private Double length;
@Column(name = "weight")
private Double weight;
@Column(name = "energy_level")
private Integer energyLevel;
// Getters and Setters
public Long getPetId() {
return petId;
}
public void setPetId(Long petId) {
this.petId = petId;
}
public String getTemperament() {
return temperament;
}
public void setTemperament(String temperament) {
this.temperament = temperament;
}
public Double getLength() {
return length;
}
public void setLength(Double length) {
this.length = length;
}
public Double getWeight() {
return weight;
}
public void setWeight(Double weight) {
this.weight = weight;
}
public Integer getEnergyLevel() {
return energyLevel;
}
public void setEnergyLevel(Integer energyLevel) {
this.energyLevel = energyLevel;
}
@Override
public String toString() {
return "PetAttributes [petId=" + petId + ", temperament=" + temperament + ", length=" + length + ", weight="
+ weight + ", energyLevel=" + energyLevel + "]";
}
}

View file

@ -0,0 +1,79 @@
package org.springframework.samples.petclinic.owner;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("/petAttributes")
public class PetAttributesController {
private final PetAttributesRepository petAttributesRepository;
private final PetAttributesService petAttributesService;
@Autowired
public PetAttributesController(PetAttributesRepository petAttributesRepository,PetAttributesService petAttributesService) {
this.petAttributesRepository = petAttributesRepository;
this.petAttributesService=petAttributesService;
}
@GetMapping("/all")
public String list(Model model) {
model.addAttribute("listAttributes", petAttributesRepository.findAll());
return "petAttributes/list";
}
@GetMapping("/new")
public String showCreateForm(Model model) {
model.addAttribute("petAttributes", new PetAttributes());
return "petAttributes/form";
}
@PostMapping("/save")
public String save(@ModelAttribute PetAttributes petAttributes, RedirectAttributes redirectAttributes) {
petAttributesRepository.save(petAttributes);
redirectAttributes.addFlashAttribute("successMessage", "Saved successfully!");
return "redirect:/petAttributes/all";
}
@GetMapping("/edit/{id}")
public String showEditForm(@PathVariable("id") Integer id, Model model) {
PetAttributes attr = petAttributesRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid ID: " + id));
model.addAttribute("petAttributes", attr);
return "petAttributes/form";
}
@GetMapping("/search")
public String showSearchForm() {
return "petAttributes/search";
}
@GetMapping(value = "/search", params = "petId")
public String searchByPetId(@RequestParam("petId") Integer petId, Model model) {
PetAttributes result = petAttributesService.findByPetId(petId);
if (result != null) {
model.addAttribute("listAttributes", List.of(result));
} else {
model.addAttribute("listAttributes", List.of());
model.addAttribute("error", "No results for Pet ID: " + petId);
}
return "petAttributes/search";
}
@PostMapping("/delete/{id}")
public String delete(@PathVariable("id") Integer id, RedirectAttributes redirectAttributes) {
petAttributesRepository.deleteById(id);
redirectAttributes.addFlashAttribute("successMessage", "Deleted successfully!");
return "redirect:/petAttributes/all";
}
}

View file

@ -0,0 +1,14 @@
package org.springframework.samples.petclinic.owner;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PetAttributesRepository extends JpaRepository<PetAttributes, Integer> {
PetAttributes findByPetId(int petId);
}

View file

@ -0,0 +1,9 @@
package org.springframework.samples.petclinic.owner;
public interface PetAttributesService {
PetAttributes save(PetAttributes attributes);
PetAttributes findByPetId(int petId);
}

View file

@ -0,0 +1,29 @@
package org.springframework.samples.petclinic.owner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PetAttributesServiceImpl implements PetAttributesService {
private final PetAttributesRepository petAttributesRepository;
@Autowired
public PetAttributesServiceImpl(PetAttributesRepository repository) {
this.petAttributesRepository = repository;
}
@Override
@Transactional
public PetAttributes save(PetAttributes attributes) {
return petAttributesRepository.save(attributes);
}
@Override
@Transactional(readOnly = true)
public PetAttributes findByPetId(int petId) {
return petAttributesRepository.findByPetId(petId);
}
}

View file

@ -0,0 +1,11 @@
package org.springframework.samples.petclinic.owner;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PetRepository extends CrudRepository<Pet, Integer> {
Pet findById(int id);
}

View file

@ -1,4 +1,5 @@
# database init, supports mysql too
database=h2
spring.sql.init.schema-locations=classpath*:db/${database}/schema.sql
spring.sql.init.data-locations=classpath*:db/${database}/data.sql
@ -23,3 +24,8 @@ logging.level.org.springframework=INFO
# Maximum time static resources should be cached
spring.web.resources.cache.cachecontrol.max-age=12h
spring.h2.console.enabled=true
# Optional: Change the console path (default is /h2-console)
spring.h2.console.path=/h2-console

View file

@ -62,3 +62,13 @@ CREATE TABLE visits (
);
ALTER TABLE visits ADD CONSTRAINT fk_visits_pets FOREIGN KEY (pet_id) REFERENCES pets (id);
CREATE INDEX visits_pet_id ON visits (pet_id);
CREATE TABLE pet_attributes (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
pet_id INTEGER NOT NULL,
temperament VARCHAR(255),
length INTEGER,
weight INTEGER,
energy_level INTEGER
);

View file

@ -46,3 +46,19 @@ visitDate=Visit Date
editOwner=Edit Owner
addNewPet=Add New Pet
petsAndVisits=Pets and Visits
petTypes=Pet Types
addPetAttributes=Add Pet Attributes
save=Save
petTypeAdded=Pet Type Added Successfully.
pet.attributes=Pet Attributes
pet.id=Pet ID
temperament=Temperament
length=Length
weight=Weight
energy.level=Energy Level
pages=Pages
petAttributes=Pet Attributes

View file

@ -4,84 +4,89 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/x-icon" th:href="@{/resources/images/favicon.png}">
<title th:text="#{layoutTitle}">PetClinic :: a Spring Framework demonstration</title>
<link th:href="@{/webjars/font-awesome/css/font-awesome.min.css}" rel="stylesheet">
<link rel="stylesheet" th:href="@{/resources/css/petclinic.css}" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/x-icon" th:href="@{/resources/images/favicon.png}">
<title th:text="#{layoutTitle}">PetClinic :: a Spring Framework demonstration</title>
<link th:href="@{/webjars/font-awesome/css/font-awesome.min.css}" rel="stylesheet">
<link rel="stylesheet" th:href="@{/resources/css/petclinic.css}" />
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark" role="navigation">
<div class="container-fluid">
<a class="navbar-brand" th:href="@{/}"><span></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar" style>
<nav class="navbar navbar-expand-lg navbar-dark" role="navigation">
<div class="container-fluid">
<a class="navbar-brand" th:href="@{/}"><span></span></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar" style>
<ul class="navbar-nav me-auto mb-2 mb-lg-0" th:remove="all">
<ul class="navbar-nav me-auto mb-2 mb-lg-0" th:remove="all">
<li th:fragment="menuItem (link,active,title,glyph,text)" th:class="nav-item">
<a th:class="${active==menu ? 'nav-link active' : 'nav-link'}" th:href="@{__${link}__}" th:title="${title}">
<span th:class="'fa fa-'+${glyph}" class="fa fa-home"></span>
<span th:text="${text}">Template</span>
</a>
</li>
<li th:fragment="menuItem (link,active,title,glyph,text)" th:class="nav-item">
<a th:class="${active==menu ? 'nav-link active' : 'nav-link'}" th:href="@{__${link}__}"
th:title="${title}">
<span th:class="'fa fa-'+${glyph}" class="fa fa-home"></span>
<span th:text="${text}">Template</span>
</a>
</li>
</ul>
</ul>
<ul class="nav navbar-nav me-auto">
<ul class="nav navbar-nav me-auto">
<li th:replace="~{::menuItem ('/','home','home page','home',#{home})}">
<span class="fa fa-home" aria-hidden="true"></span>
<span th:text="#{home}">Home</span>
</li>
<li th:replace="~{::menuItem ('/','home','home page','home',#{home})}">
<span class="fa fa-home" aria-hidden="true"></span>
<span th:text="#{home}">Home</span>
</li>
<li th:replace="~{::menuItem ('/owners/find','owners','find owners','search',#{findOwners})}">
<span class="fa fa-search" aria-hidden="true"></span>
<span th:text="#{findOwners}">Find owners</span>
</li>
<li th:replace="~{::menuItem ('/owners/find','owners','find owners','search',#{findOwners})}">
<span class="fa fa-search" aria-hidden="true"></span>
<span th:text="#{findOwners}">Find owners</span>
</li>
<li th:replace="~{::menuItem ('/vets.html','vets','veterinarians','th-list',#{vets})}">
<span class="fa fa-th-list" aria-hidden="true"></span>
<span th:text="#{vets}">Veterinarians</span>
</li><i class="fa fa-paw"></i> Pet Attributes
</a>
</li>
<li
th:replace="~{::menuItem ('/petAttributes/all','petAttributes','Pet Attributes','paw',#{petAttributes})}">
</li>
<li th:replace="~{::menuItem ('/vets.html','vets','veterinarians','th-list',#{vets})}">
<span class="fa fa-th-list" aria-hidden="true"></span>
<span th:text="#{vets}">Veterinarians</span>
</li>
<li
th:replace="~{::menuItem ('/oups','error','trigger a RuntimeException to see how it is handled','exclamation-triangle',#{error})}">
<span class="fa exclamation-triangle" aria-hidden="true"></span>
<span th:text="#{error}">Error</span>
</li>
<li
th:replace="~{::menuItem ('/oups','error','trigger a RuntimeException to see how it is handled','exclamation-triangle',#{error})}">
<span class="fa exclamation-triangle" aria-hidden="true"></span>
<span th:text="#{error}">Error</span>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="container xd-container">
</ul>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="container xd-container">
<th:block th:insert="${template}" />
<th:block th:insert="${template}" />
<br />
<br />
<div class="container">
<div class="row">
<div class="col-12 text-center">
<img src="../static/images/spring-logo.svg" th:src="@{/resources/images/spring-logo.svg}"
alt="VMware Tanzu Logo" class="logo">
</div>
</div>
</div>
</div>
</div>
<br />
<br />
<div class="container">
<div class="row">
<div class="col-12 text-center">
<img src="../static/images/spring-logo.svg" th:src="@{/resources/images/spring-logo.svg}"
alt="VMware Tanzu Logo" class="logo">
</div>
</div>
</div>
</div>
</div>
<script th:src="@{/webjars/bootstrap/dist/js/bootstrap.bundle.min.js}"></script>
<script th:src="@{/webjars/bootstrap/dist/js/bootstrap.bundle.min.js}"></script>
</body>

View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body}, 'Pet Attributes Form')}">
<body>
<h2 th:text="${petAttributes.id} != null ? 'Edit' : 'Add'">Pet Attributes</h2>
<form th:action="@{/petAttributes/save}" th:object="${petAttributes}" method="post">
<input type="hidden" th:field="*{id}"/>
<!-- Show hidden petId input only for Edit -->
<th:block th:if="${petAttributes.id != null}">
<input type="hidden" th:field="*{petId}" />
</th:block>
<!-- Show visible petId input only for Add -->
<th:block th:unless="${petAttributes.id != null}">
<label>Pet ID:</label>
<input type="number" th:field="*{petId}" class="form-control" /><br/>
</th:block>
<!--<label>Pet ID:</label>
<input type="number" th:field="*{petId}" class="form-control"/><br/>-->
<label>Temperament:</label>
<input type="text" th:field="*{temperament}" class="form-control"/><br/>
<label>Length:</label>
<input type="number" th:field="*{length}" class="form-control" step="0.01"/><br/>
<label>Weight:</label>
<input type="number" th:field="*{weight}" class="form-control" step="0.01"/><br/>
<label>Energy Level:</label>
<input type="number" th:field="*{energyLevel}" class="form-control"/><br/>
<button type="submit" class="btn btn-success">Save</button>
<a class="btn btn-secondary" th:href="@{/petAttributes}">Cancel</a>
</form>
</body>
</html>

View file

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
th:replace="~{fragments/layout :: layout (~{::body}, 'Pet Attributes')}">
<body>
<h2>Pet Attributes</h2>
<a class="btn btn-primary" th:href="@{/petAttributes/new}">Add New</a>
<a class="btn btn-primary" th:href="@{/petAttributes/search}">Search by Pet ID</a>
<table class="table table-bordered">
<thead>
<tr>
<!--<th>ID</th>-->
<th>Pet ID</th>
<th>Temperament</th>
<th>Length</th>
<th>Weight</th>
<th>Energy Level</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr th:each="attr : ${listAttributes}">
<!-- <td th:text="${attr.id}">1</td>-->
<td th:text="${attr.petId}">100</td>
<td th:text="${attr.temperament}">Calm</td>
<td th:text="${attr.length}">25.0</td>
<td th:text="${attr.weight}">10.0</td>
<td th:text="${attr.energyLevel}">4</td>
<td>
<a th:href="@{'/petAttributes/edit/' + ${attr.id}}">Edit</a> |
<!-- POST-based delete form -->
<form th:action="@{'/petAttributes/delete/' + ${attr.id}}" method="post"
style="display:inline;">
<button type="submit" class="btn btn-link p-0" onclick="return confirm('Delete?')">
Delete
</button>
</form>
</td>
</tr>
</tbody>
</table>
<div th:if="${successMessage}" class="alert alert-success" th:text="${successMessage}"></div>
</body>
</html>

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org" th:replace="~{fragments/layout :: layout (~{::body},'vets')}">
<body>
<h2 th:text="#{vets}">Veterinarians</h2>
<table id="vets" class="table table-striped">
<thead>
<tr>
<th th:text="#{name}">Name</th>
<th th:text="#{specialties}">Specialties</th>
</tr>
</thead>
<tbody>
<tr th:each="vet : ${listVets}">
<td th:text="${vet.firstName + ' ' + vet.lastName}"></td>
<td>
<span th:each="specialty : ${vet.specialties}" th:text="${specialty.name + ' '}" /> <span
th:if="${vet.nrOfSpecialties == 0}" th:text="#{none}">none</span>
</td>
</tr>
</tbody>
</table>
<div th:if="${totalPages > 1}">
<span th:text="#{pages}">Pages:</span>
<span>[</span>
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/vets.html?page=__${i}__'}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
<span>]&nbsp;</span>
<span>
<a th:if="${currentPage > 1}" th:href="@{'/vets.html?page=1'}" th:title="#{first}"
class="fa fa-fast-backward"></a>
<span th:unless="${currentPage > 1}" th:title="#{first}" class="fa fa-fast-backward"></span>
</span>
<span>
<a th:if="${currentPage > 1}" th:href="@{'/vets.html?page=__${currentPage - 1}__'}" th:title="#{previous}"
class="fa fa-step-backward"></a>
<span th:unless="${currentPage > 1}" th:title="#{previous}" class="fa fa-step-backward"></span>
</span>
<span>
<a th:if="${currentPage < totalPages}" th:href="@{'/vets.html?page=__${currentPage + 1}__'}" th:title="#{next}"
class="fa fa-step-forward"></a>
<span th:unless="${currentPage < totalPages}" th:title="#{next}" class="fa fa-step-forward"></span>
</span>
<span>
<a th:if="${currentPage < totalPages}" th:href="@{'/vets.html?page=__${totalPages}__'}" th:title="#{last}"
class="fa fa-fast-forward"></a>
<span th:unless="${currentPage < totalPages}" th:title="#{last}" class="fa fa-fast-forward"></span>
</span>
</div>
</body>
</html>

View file

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
th:replace="~{fragments/layout :: layout(~{::template}, 'petAttributes')}">
<th:block th:fragment="template">
<h2>Search by Pet ID</h2>
<form th:action="@{/petAttributes/search}" method="get">
<div class="form-group">
<label for="petId">Pet ID:</label>
<input type="number" class="form-control" id="petId" name="petId" required>
</div>
<button type="submit" class="btn btn-primary mt-2">Search</button>
</form>
<div th:if="${error}" class="alert alert-warning mt-3" th:text="${error}"></div>
<div th:if="${listAttributes}">
<h3 class="mt-4">Results</h3>
<table class="table table-bordered">
<thead>
<tr>
<!-- <th>ID</th>-->
<th>Pet ID</th>
<th>Temperament</th>
<th>Length</th>
<th>Weight</th>
<th>Energy Level</th>
</tr>
</thead>
<tbody>
<tr th:each="attr : ${listAttributes}">
<!-- <td th:text="${attr.id}"></td>-->
<td th:text="${attr.petId}"></td>
<td th:text="${attr.temperament}"></td>
<td th:text="${attr.length}"></td>
<td th:text="${attr.weight}"></td>
<td th:text="${attr.energyLevel}"></td>
</tr>
</tbody>
</table>
</div>
</th:block>
</html>