Skip to content
Snippets Groups Projects
Commit 121ce7db authored by Marnuri Nitish -'s avatar Marnuri Nitish -
Browse files

Add delete functionality, info data and exception handling

parent a7404aff
No related branches found
No related tags found
No related merge requests found
Showing
with 863 additions and 157 deletions
......@@ -4,6 +4,7 @@ import org.springframework.web.bind.annotation.*;
import polish_community_group_11.polish_community.information.models.DBInfo;
import polish_community_group_11.polish_community.information.services.InformationService;
import java.sql.SQLException;
import java.util.List;
......@@ -22,7 +23,7 @@ public class InformationApiController {
}
@DeleteMapping("api/info/delete")
public int deleteInformation(@RequestParam List<Integer> deleteIdList){
public int deleteInformation(@RequestParam List<Integer> deleteIdList) throws SQLException {
return infoService.deleteInfoByList(deleteIdList);
}
}
......@@ -61,8 +61,7 @@ public class InformationController {
@PostMapping("/info/add")
public ModelAndView addInformation(@Valid @ModelAttribute("newInfo") DBInfoForm newInfo,
BindingResult bindingResult, Model model)
{
BindingResult bindingResult, Model model) throws SQLException {
ModelAndView modelAndView = new ModelAndView("information/addInformation");
if(bindingResult.hasErrors()){
modelAndView.addObject(model.asMap());
......@@ -89,7 +88,7 @@ public class InformationController {
@PostMapping("/info/edit")
public ModelAndView editInformation(@Valid @ModelAttribute("newInfo") DBInfoForm newInfo,
BindingResult bindingResult, Model model){
BindingResult bindingResult, Model model) throws SQLException {
ModelAndView modelAndView = new ModelAndView("information/addInformation");
if(bindingResult.hasErrors()){
modelAndView.addObject(model.asMap());
......
......@@ -8,8 +8,12 @@ import java.sql.SQLException;
import java.util.List;
/**
* This Information Repository interface acts as a layer to hide the main implementations of the
information repository methods and make only the methods available.
It defines methods to retrieve specific data
items and lists of database information from the data source.*/
items and lists of database information from the data source.
**/
public interface InformationRepository {
public DBInfo getInfomrationById(int id)
......@@ -17,7 +21,7 @@ public interface InformationRepository {
public List<DBInfo> getDbInfoListByCategory(int category_id);
List<DBInfo> findDbInfo(String userInput);
void addNewInfo(DBInfo newInfo);
int deleteInfoList(List<Integer> deleteIdList);
int editInfo(DBInfo updatedInfo);
void addNewInfo(DBInfo newInfo) throws SQLException;
int deleteInfoList(List<Integer> deleteIdList) throws SQLException;
int editInfo(DBInfo updatedInfo) throws SQLException;
}
......@@ -97,40 +97,59 @@ public class InformationRepositoryImpl implements InformationRepository {
return jdbc.query(sql, ps -> ps.setString(1, "%" + userInput.toLowerCase() + "%"), infoItemMapper);
}
public void addNewInfo(DBInfo newInfo){
// Inserts new database of information record to the information table
public void addNewInfo(DBInfo newInfo) throws SQLException {
String dbInsertSql =
"insert into information " +
"(category_id, info_title, created_by, created_date, " +
"updated_date, tag_id, short_info, info_article)" +
" values (?,?,?,?,?,?,?,?)";
try {
jdbc.update(dbInsertSql,
newInfo.getCategoryId(),
newInfo.getInfoTitle(),
"Nitish Marnuri",
"Nitish Marnuri", //Mocking the user for now
LocalDate.now(),
LocalDate.now(),
1,
1, // Mocking the tag id for now
newInfo.getShortDescription(),
newInfo.getInfoDescription()
);
} catch (DataAccessException e) {
throw new SQLException("Failed to insert new information record", e);
}
}
public int deleteInfoList(List<Integer> deleteIdList){
// Deletes a list of records from information table with list of Information Ids
public int deleteInfoList(List<Integer> deleteIdList) throws SQLException {
// Prepare query for deletion
String deleteSql = "delete from information where info_id in (" +
String.join(",", deleteIdList.stream().map(info_id -> "?").toArray(String[]::new)) + ")";
Object[] deleteParams = deleteIdList.toArray();
try {
return jdbc.update(deleteSql, deleteParams);
} catch (DataAccessException e) {
throw new SQLException("Failed to delete information records", e);
}
}
public int editInfo(DBInfo updatedInfo){
// Updates selected record with new updated information
public int editInfo(DBInfo updatedInfo) throws SQLException {
String updateSql = "update information " +
"set info_title=?, short_info=?, info_article=? " +
"where info_id=?";
return jdbc.update(updateSql, updatedInfo.getInfoTitle(),
try {
return jdbc.update(updateSql,
updatedInfo.getInfoTitle(),
updatedInfo.getShortDescription(),
updatedInfo.getInfoDescription(),
updatedInfo.getInformationId());
updatedInfo.getInformationId()
);
} catch (DataAccessException e) {
throw new SQLException("Failed to update information record", e);
}
}
}
......@@ -7,6 +7,10 @@ import lombok.NoArgsConstructor;
import java.time.LocalDate;
/*
This is a view model class DB Info form to take form data and perform validations.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
......@@ -14,12 +18,15 @@ public class DBInfoForm {
private int informationId;
private int categoryId;
private int tagId;
// Add validations for not empty fields
@NotEmpty(message = "Please enter a title for the new information heading")
private String infoTitle;
@NotEmpty(message = "Please enter a short description")
private String shortDescription;
@NotEmpty(message = "Please add the detailed content information for the new information.")
private String infoDescription;
private String createdBy;
private LocalDate createdDate;
private LocalDate updatedDate;
......
......@@ -23,8 +23,8 @@ public interface InformationService {
List<SearchResult> searchInfo(String userInput);
DBInfo processDbInfoForm(DBInfoForm dbInfoForm);
void addNewInfo(DBInfo newInfo);
int deleteInfoByList(List<Integer> deleteIdList);
int editInfo(DBInfo updatedInfo);
void addNewInfo(DBInfo newInfo) throws SQLException;
int deleteInfoByList(List<Integer> deleteIdList) throws SQLException;
int editInfo(DBInfo updatedInfo) throws SQLException;
DBInfoForm processDbInfoFormForEdit(DBInfo selectedInfo);
}
......@@ -48,19 +48,22 @@ public class InformationServiceImpl implements InformationService{
.toList();
}
public void addNewInfo(DBInfo newInfo){
public void addNewInfo(DBInfo newInfo) throws SQLException {
informationRepository.addNewInfo(newInfo);
}
public int deleteInfoByList(List<Integer> deleteIdList){
public int deleteInfoByList(List<Integer> deleteIdList) throws SQLException {
return informationRepository.deleteInfoList(deleteIdList);
}
public int editInfo(DBInfo updatedInfo){
public int editInfo(DBInfo updatedInfo) throws SQLException {
return informationRepository.editInfo(updatedInfo);
}
public DBInfo processDbInfoForm(DBInfoForm dbInfoForm) {
if (dbInfoForm == null) {
throw new IllegalArgumentException("dbInfoForm cannot be null");
}
DBInfo newInfo = new DBInfoImpl();
newInfo.setCategoryId(dbInfoForm.getCategoryId());
newInfo.setInfoTitle(dbInfoForm.getInfoTitle());
......@@ -74,6 +77,9 @@ public class InformationServiceImpl implements InformationService{
}
public DBInfoForm processDbInfoFormForEdit(DBInfo selectedInfo) {
if (selectedInfo == null) {
throw new IllegalArgumentException("dbInfoForm cannot be null");
}
DBInfoForm selectedInfoForm = new DBInfoForm();
selectedInfoForm.setCategoryId(selectedInfo.getCategoryId());
selectedInfoForm.setInfoTitle(selectedInfo.getInfoTitle());
......
......@@ -17,5 +17,5 @@ spring.sql.init.mode=always
server.error.whitelabel.enabled=false
server.error.path=/error
spring.jpa.hibernate.ddl-auto=create
spring.sql.init.schema-locations=classpath:/schema.sql
spring.sql.init.data-locations=classpath:/data.sql, classpath:/data_information_domains.sql
\ No newline at end of file
......@@ -3,92 +3,13 @@ delete from information;
delete from categories;
INSERT INTO categories (category_title, category_description, user_id)
VALUES
('Educational Institutions', 'Information about schools, colleges, and educational programs available in the community', 1),
('Emergency Services Guide', 'Important contact numbers and procedures for various emergency situations', 2),
('Local Business Directory', 'A list of all registered businesses in our community including contact information', 3),
('Environmental Initiatives', 'Details on ongoing and upcoming environmental projects and how residents can paritcipate.', 4),
('Immigration', 'Essential guidance on visas, residency permits, work authorizations, and legal requirements for moving to and living in the UK', 5);
-- mock data for information
insert into information (category_id, info_title, created_by, created_date, updated_date, tag_id, short_info, info_article)
values (1, 'Education Information for Polish Nationals in the UK', 'Krzysztof Nowak', CURRENT_DATE, CURRENT_DATE, 3,'', '# **Education System in the UK for Polish Nationals**
This guide is designed to help Polish nationals in the UK navigate the education system, from primary school to higher education. It offers information on enrollment procedures, qualifications, and financial support available for Polish students.
---
## **Enrollment Procedures**
- Children of Polish nationals are entitled to free primary and secondary education in the UK.
- For higher education, Polish students can apply for UK universities, often requiring proof of previous educational qualifications.
---
## **Key Education Policies**
- Polish students in the UK can apply for student loans or financial assistance, depending on residency status and eligibility.
- Many universities offer support services for international students, including language assistance and career advice.
> **Tip:** It''s important to check with the university for specific entry requirements related to your previous education in Poland.
---
## **Post-Graduation Opportunities**
- Polish students graduating from UK universities are eligible for the Graduate Route visa, allowing them to stay and work in the UK for two years.
---
## **Useful Links & Resources**
- [UK Government Education Portal](https://www.gov.uk/education)
- [Polish Community in the UK Education Support](https://www.polandinuk.co.uk)'),
(2, 'Healthcare Access for Polish Nationals in the UK', 'Marek Jablonski', CURRENT_DATE, CURRENT_DATE, 4,'', '# **Navigating Healthcare in the UK for Polish Nationals**
As a Polish national residing in the UK, you have access to healthcare services through the NHS (National Health Service). This guide will explain how to register with a GP (General Practitioner), access emergency services, and apply for healthcare-related assistance.
---
## **Registering with a GP**
- To receive NHS healthcare, you must first register with a GP.
- Bring proof of your address and identification when registering.
---
## **Emergency Healthcare**
- In emergencies, dial 999 for an ambulance or visit your nearest Accident and Emergency (A&E) department.
- Polish citizens are eligible for free emergency treatment.
---
## **Health Insurance and Costs**
- Polish nationals who are employed in the UK contribute to the National Insurance system, which covers many healthcare costs.
- For non-residents or those without work, certain services may require payment.
---
## **Important Notes**
- Ensure to carry your NHS number when visiting health services to avoid delays in treatment.
- If you’re planning to travel outside the UK, consider travel health insurance.
---
## **Useful Links & Resources**
- [NHS Services for Overseas Nationals](https://www.nhs.uk/using-the-nhs/healthcare-when-abroad)
- [Polish Healthcare Support in the UK](https://www.polishcommunity.org.uk)'),
(3, 'Find legal advice and information', 'Jane Doe', CURRENT_DATE, CURRENT_DATE, 4, '','Overview
You can get legal advice and information to solve common problems, for example about:
debts
decisions about benefits
discrimination at work
immigration statuses
disagreements with neighbours
making arrangements for children, money or property if you divorce or separate
Get advice and information as early as you can. It might stop a problem from getting worse.
You may need to find a legal adviser with specialist training in the area of your problem, for example a solicitor. They could help you solve it, or give you advice about what to do next.
You might be able to solve the problem without going to court, for example by working with a mediator.
If you need to go to court to solve the problem, you can find out how to prepare for a court hearing.');
('Work', 'Information about employment opportunities, workplace rights, and support available to migrants in Wales', 1),
('Housing', 'Guidance on finding accommodation, understanding tenancy agreements, and accessing housing support services', 2),
('Health and Social care', 'Information on accessing healthcare services, registering with a GP, and support for social care needs', 3),
('Social connections', 'Opportunities and programs to foster integration and build friendships between migrants and local communities', 4),
('Education and Skills', 'Details about schools, colleges, training programs, and opportunities for skill development in Wales', 5),
('Safety and Stability', 'Important information on maintaining personal safety, reporting hate crimes, and accessing support for victims of crime', 6),
('Rights and Responsibilities', 'Guidance on legal rights, civic responsibilities, and how migrants can actively participate in Welsh society', 7);
-- mock data for event
......
This diff is collapsed.
......@@ -96,7 +96,7 @@
/*background-color: #9a1750;*/
}
main{
.main-content{
max-width: 100%;
/*background-color: #e3e2df;*/
color: var(--primary-color);
......
......@@ -59,12 +59,15 @@ document.getElementById('category-name').textContent = categoryName ? `${categor
// Fetch and render the category headings when the page loads
fetchCategoryHeadings();
// Get the category id from the url
function getCategoryIdFromUrl() {
const path = window.location.pathname;
const parts = path.split("/");
return parts[2];
}
// Function to dynamically add href url to add information button
function generateAddInfoPageUrl() {
// Extract categoryId from the URL
const categoryId = getCategoryIdFromUrl();
......@@ -78,9 +81,7 @@ function generateAddInfoPageUrl() {
generateAddInfoPageUrl();
/*new code*/
// Function to toggle edit mode (remains unchanged)
// Function to toggle edit mode to enable multi-select for deletion
function toggleEditMode() {
const container = document.getElementById('headings-container');
const editButton = document.getElementById('edit-mode-button');
......@@ -90,20 +91,22 @@ function toggleEditMode() {
const listItems = container.querySelectorAll('.list-item');
if (isEditMode) {
// Add checkboxes to each list item
// Add checkboxes to each heading list item
listItems.forEach(item => {
if (!item.querySelector('input[type="checkbox"]')) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.classList.add('multi-select-checkbox');
checkbox.addEventListener('change', toggleDeleteButtonState);
// Inserts the checkbox before the first child of the list item
item.insertBefore(checkbox, item.firstChild);
}
});
editButton.textContent = 'Done';
// Enable the delete button
deleteButton.disabled = false;
deleteButton.style.visibility="visible"
// Enable the delete button
} else {
// Remove checkboxes from each list item
listItems.forEach(item => {
......@@ -113,8 +116,9 @@ function toggleEditMode() {
}
});
editButton.textContent = 'Edit';
// Disables and hides the delete button
deleteButton.disabled = true;
deleteButton.style.visibility="hidden" // Disable the delete button
deleteButton.style.visibility="hidden"
}
}
......@@ -128,28 +132,32 @@ function toggleDeleteButtonState() {
// Function to delete selected items
async function deleteSelectedItems() {
const selectedItems = document.querySelectorAll('.multi-select-checkbox:checked');
debugger;
// Get the list of heading ids to be deleted
const idsToDelete = Array.from(selectedItems).map(checkbox => {
const listItem = checkbox.closest('.list-item');
const anchor = listItem.querySelector('a.heading-link');
if (anchor) {
const href = anchor.getAttribute('href'); // e.g., "info/{id}"
const href = anchor.getAttribute('href');
const idMatch = href.match(/info\/(\d+)/); // Extract the ID using regex
return idMatch ? idMatch[1] : null; // Return the ID if matched
}
return null; // Skip if no anchor or invalid format
return null; // Skip if no anchor element or invalid format
}).filter(id => id !== null);
if (idsToDelete.length === 0) {
alert("No items selected for deletion.");
alert("No headings selected for deletion.");
return;
}
const confirmed = confirm("Are you sure you want to delete the selected items?");
// Check confirmation before deletion
const confirmed = confirm("Are you sure you want to delete the selected list of headings?");
if (!confirmed) return;
// Convert array of IDs to a comma-separated string
// Convert array of heading IDs to a comma-separated string to be passed as a parameter
const idList = idsToDelete.join(',');
// Fetch api call to delete the list of headings at server side
try {
const response = await fetch('/api/info/delete', {
method: 'DELETE',
......@@ -177,6 +185,6 @@ async function deleteSelectedItems() {
}
}
// Attach event listeners
// Attach event listeners to toggle edit button and perform deletion
document.getElementById('edit-mode-button').addEventListener('click', toggleEditMode);
document.getElementById('delete-button').addEventListener('click', deleteSelectedItems);
\ No newline at end of file
......@@ -5,20 +5,31 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Information</title>
<link rel="stylesheet" href="/css/information/AddInfoStyles.css">
<!-- Stylesheet for rich text editor-->
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
<script defer src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<!-- Scripts for converting markdown to html and vice-versa.-->
<script defer src="https://cdn.jsdelivr.net/npm/turndown@7.0.0/dist/turndown.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.2/lib/marked.umd.min.js"></script>
<!-- Custom scripts-->
<link rel="stylesheet" href="/css/information/AddInfoStyles.css">
<script defer src="/js/information/addInfoScript.js"></script>
<script defer src="/js/information/infoScript.js"></script>
</head>
<!--Removed <body> tags as we are creating each feature as a layout component-->
<section layout:fragment="content">
<a href="#" id="backToHeadings" class="backButton">
<strong><p><i class="bi bi-chevron-left"></i>
<span id="backArrowTitle"> Back to headings</span></p></strong>
</a>
<!-- Using same page for both edit and add information feature-->
<h1 th:text="${formAction.contains('edit')} ? 'Edit Information' : 'Add New Information'"></h1>
<!-- Get action method from the controller based on the operation for add/edit-->
<form th:action="@{${formAction}}" th:method="post" th:object="${newInfo}" id="addInfoForm">
<fieldset>
<input th:field="*{informationId}" type="hidden"/>
......@@ -34,12 +45,18 @@
<label th:for="*{shortDescription}">Short Description</label>
<textarea th:field="*{shortDescription}"></textarea>
<div class="alert" th:errors="*{shortDescription}" th:if="${#fields.hasErrors('shortDescription')}">Short description Error</div>
<!--Rich text editor for storing formatted text -->
<label for="editor">Content</label>
<div id="editor"></div>
<div class="alert" th:errors="*{infoDescription}" th:if="${#fields.hasErrors('infoDescription')}">Description Error</div>
</fieldset>
<div class="buttons">
<!--Add redirection dynamically through js-->
<a href="#" id="cancelButton" class="button cancel">Cancel</a>
<!--Change button based on the operation-->
<button id="submitButton" th:text="${formAction.contains('edit')}?'Update Information':'Add Information'" type="submit" class="submit"></button>
</div>
</form>
......
......@@ -3,32 +3,37 @@
layout:decorate="~{layout/layout}">
<head>
<meta charset="UTF-8">
<!--Custom styles-->
<link rel="stylesheet" href="/css/information/infoStyles.css">
<!-- <script src="https://cdn.jsdelivr.net/npm/marked@15.0.2/lib/marked.umd.min.js"></script>-->
<link rel="stylesheet" href="/css/infoHeadingFragment/infoHeadingFragment.css">
<!--Custom scripts -->
<script src="/js/infoHeadingFragment/infoHeadingFragment.js" defer></script>
<script src="/js/information/infoScript.js" defer></script>
<title th:text="${selectedInfo.getInfoTitle()}">DB Info</title>
</head>
<!--Removed <body> tag as it is added as a component to the main layout-->
<section layout:fragment="content">
<div th:replace="~{infoHeadingFragment/infoHeadingFragment :: breadcrumb}"></div>
<div class="container">
<div class="head-container">
<div class="sub-head-main-container">
<main class="container">
<section class="head-container">
<section class="sub-head-main-container">
<a id="backToHeadings" href="#"><img src="/assets/back-arrow.png" alt="back-arrow"></a>
<h1 th:text="${selectedInfo.getInfoTitle()}">Information Title</h1>
</div>
<div class="sub-head-edit-container">
</section>
<aside class="sub-head-edit-container">
<a id="editButtonLink" th:href="@{/info/edit/{id} (id=${selectedInfo.getInformationId()})}">
<button><strong>Edit</strong></button></a>
</div>
</div>
<main>
</aside>
</section>
<section class="main-content">
<!-- Main content section displaying the information markup -->
<section id="markupcontent" th:utext="${html}">
<article id="markupcontent" th:utext="${html}">
<!-- Dynamic content will be inserted here -->
</article>
</section>
</main>
</div>
</section>
</html>
\ No newline at end of file
package polish_community_group_11.polish_community.information;
import org.junit.jupiter.api.BeforeAll;
import polish_community_group_11.polish_community.information.dao.InformationRepositoryImpl;
import polish_community_group_11.polish_community.information.models.DBInfo;
import polish_community_group_11.polish_community.information.services.InformationService;
import polish_community_group_11.polish_community.information.services.InformationServiceImpl;
import java.util.List;
import static org.skyscreamer.jsonassert.JSONAssert.assertEquals;
public class TestInformation {
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment