From a9cc0a912255c60f3006b53114947c31dc597cec Mon Sep 17 00:00:00 2001 From: mukeshs Date: Thu, 6 Nov 2025 14:52:45 +0530 Subject: [PATCH] publication added newly --- .../controller/PublicationResource.java | 140 ++++ .../backend/domain/Publication.java | 74 ++ .../domain/PublicationNotFoundException.java | 8 + .../repository/PublicationRepository.java | 22 + .../backend/service/PublicationService.java | 33 + .../service/impl/PublicationServiceImpl.java | 129 +++ .../src/main/resources/application.yml | 2 +- .../src/app/admin/admin-routing.module.ts | 2 + .../src/app/admin/admin.module.ts | 4 +- support-portal-frontend/src/app/app.module.ts | 2 + .../hero-image/hero-image.component.css | 2 +- .../app/component/menu/menu.component.html | 42 +- .../publications/publications.component.css | 742 ++++++++++++++++++ .../publications/publications.component.html | 506 ++++++++++++ .../publications.component.spec.ts | 25 + .../publications/publications.component.ts | 300 +++++++ .../src/app/model/publication.model.ts | 16 + .../src/app/service/publication.service.ts | 90 +++ 18 files changed, 2118 insertions(+), 21 deletions(-) create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/PublicationResource.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Publication.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/domain/PublicationNotFoundException.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/PublicationRepository.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/PublicationService.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/impl/PublicationServiceImpl.java create mode 100644 support-portal-frontend/src/app/component/publications/publications.component.css create mode 100644 support-portal-frontend/src/app/component/publications/publications.component.html create mode 100644 support-portal-frontend/src/app/component/publications/publications.component.spec.ts create mode 100644 support-portal-frontend/src/app/component/publications/publications.component.ts create mode 100644 support-portal-frontend/src/app/model/publication.model.ts create mode 100644 support-portal-frontend/src/app/service/publication.service.ts diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/PublicationResource.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/PublicationResource.java new file mode 100644 index 0000000..78a30ee --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/controller/PublicationResource.java @@ -0,0 +1,140 @@ +package net.shyshkin.study.fullstack.supportportal.backend.resource; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.shyshkin.study.fullstack.supportportal.backend.domain.HttpResponse; +import net.shyshkin.study.fullstack.supportportal.backend.domain.Publication; +import net.shyshkin.study.fullstack.supportportal.backend.service.PublicationService; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/publications") +@RequiredArgsConstructor +@Slf4j +@CrossOrigin +public class PublicationResource { + + private final PublicationService publicationService; + + @PostMapping + @PreAuthorize("hasAnyAuthority('user:create')") + public ResponseEntity addPublication( + @RequestParam String title, + @RequestParam String authors, + @RequestParam Integer year, + @RequestParam String journal, + @RequestParam(required = false) String doi, + @RequestParam(required = false) String category, + @RequestParam(required = false) String abstractText, + @RequestParam(required = false) String publicationDate, + @RequestParam(required = false) String keywords, + @RequestParam(required = false) Integer displayOrder) { + + log.info("Adding new publication: {}", title); + Publication publication = publicationService.addPublication( + title, authors, year, journal, doi, category, abstractText, publicationDate, keywords, displayOrder + ); + return ResponseEntity.status(HttpStatus.CREATED).body(publication); + } + + @PutMapping("/{id}") + @PreAuthorize("hasAnyAuthority('user:update')") + public ResponseEntity updatePublication( + @PathVariable Long id, + @RequestParam String title, + @RequestParam String authors, + @RequestParam Integer year, + @RequestParam String journal, + @RequestParam(required = false) String doi, + @RequestParam(required = false) String category, + @RequestParam(required = false) String abstractText, + @RequestParam(required = false) String publicationDate, + @RequestParam(required = false) String keywords, + @RequestParam(required = false) Integer displayOrder) { + + log.info("Updating publication with id: {}", id); + Publication publication = publicationService.updatePublication( + id, title, authors, year, journal, doi, category, abstractText, publicationDate, keywords, displayOrder + ); + return ResponseEntity.ok(publication); + } + + @GetMapping("/active") + public ResponseEntity> getActivePublications() { + log.info("Getting active publications"); + List publications = publicationService.getActivePublications(); + return ResponseEntity.ok(publications); + } + + @GetMapping("/{id}") + public ResponseEntity getPublicationById(@PathVariable Long id) { + log.info("Getting publication with id: {}", id); + Publication publication = publicationService.getPublicationById(id); + return ResponseEntity.ok(publication); + } + + @GetMapping + @PreAuthorize("hasAnyAuthority('user:read')") + public ResponseEntity> getAllPublications() { + log.info("Getting all publications"); + List publications = publicationService.getAllPublications(); + return ResponseEntity.ok(publications); + } + + @GetMapping("/category/{category}") + public ResponseEntity> getPublicationsByCategory(@PathVariable String category) { + log.info("Getting publications by category: {}", category); + List publications = publicationService.getPublicationsByCategory(category); + return ResponseEntity.ok(publications); + } + + @GetMapping("/year/{year}") + public ResponseEntity> getPublicationsByYear(@PathVariable Integer year) { + log.info("Getting publications by year: {}", year); + List publications = publicationService.getPublicationsByYear(year); + return ResponseEntity.ok(publications); + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasAnyAuthority('user:delete')") + public ResponseEntity deletePublication(@PathVariable Long id) { + log.info("Deleting publication with id: {}", id); + publicationService.deletePublication(id); + return ResponseEntity.ok( + HttpResponse.builder() + .httpStatusCode(HttpStatus.OK.value()) + .httpStatus(HttpStatus.OK) + .reason(HttpStatus.OK.getReasonPhrase()) + .message("Publication deleted successfully") + .build() + ); + } + + @PutMapping("/{id}/toggle-active") + @PreAuthorize("hasAnyAuthority('user:update')") + public ResponseEntity toggleActiveStatus(@PathVariable Long id) { + log.info("Toggling active status for publication with id: {}", id); + Publication publication = publicationService.toggleActiveStatus(id); + return ResponseEntity.ok(publication); + } + + @PutMapping("/reorder") + @PreAuthorize("hasAnyAuthority('user:update')") + public ResponseEntity reorderPublications(@RequestBody List orderedIds) { + log.info("Reordering publications"); + publicationService.reorderPublications(orderedIds); + return ResponseEntity.ok( + HttpResponse.builder() + .httpStatusCode(HttpStatus.OK.value()) + .httpStatus(HttpStatus.OK) + .reason(HttpStatus.OK.getReasonPhrase()) + .message("Publications reordered successfully") + .build() + ); + } +} \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Publication.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Publication.java new file mode 100644 index 0000000..df0fcbd --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/domain/Publication.java @@ -0,0 +1,74 @@ +package net.shyshkin.study.fullstack.supportportal.backend.domain; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.io.Serializable; +import java.util.Date; + +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "publications") +public class Publication implements Serializable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false, updatable = false) + private Long id; + + @Column(columnDefinition = "TEXT", nullable = false) + private String title; + + @Column(columnDefinition = "TEXT") + private String authors; // Stored as comma-separated string + + @Column(nullable = false) + private Integer year; + + @Column(columnDefinition = "TEXT", nullable = false) + private String journal; + + @Column(columnDefinition = "TEXT") + private String doi; + + @Column(length = 100) + private String category; + + @Column(columnDefinition = "LONGTEXT") + private String abstractText; + + @Column(length = 100) + private String publicationDate; + + @Column(columnDefinition = "TEXT") + private String keywords; // Stored as comma-separated string + + @JsonProperty("isActive") + @Column(name = "is_active") + private boolean isActive; + + @Column(name = "display_order") + private Integer displayOrder; + + @Temporal(TemporalType.TIMESTAMP) + private Date createdDate; + + @Temporal(TemporalType.TIMESTAMP) + private Date lastModified; + + @PrePersist + protected void onCreate() { + createdDate = new Date(); + lastModified = new Date(); + } + + @PreUpdate + protected void onUpdate() { + lastModified = new Date(); + } +} diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/domain/PublicationNotFoundException.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/domain/PublicationNotFoundException.java new file mode 100644 index 0000000..1ec3eb5 --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/exception/domain/PublicationNotFoundException.java @@ -0,0 +1,8 @@ +package net.shyshkin.study.fullstack.supportportal.backend.exception.domain; + +public class PublicationNotFoundException extends RuntimeException { + + public PublicationNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/PublicationRepository.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/PublicationRepository.java new file mode 100644 index 0000000..4d493f7 --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/repository/PublicationRepository.java @@ -0,0 +1,22 @@ +package net.shyshkin.study.fullstack.supportportal.backend.repository; + +import net.shyshkin.study.fullstack.supportportal.backend.domain.Publication; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface PublicationRepository extends JpaRepository { + + @Query("SELECT p FROM Publication p WHERE p.isActive = true ORDER BY p.year DESC, p.displayOrder ASC, p.id DESC") + List findByIsActiveTrueOrderByYearDesc(); + + @Query("SELECT p FROM Publication p ORDER BY p.year DESC, p.displayOrder ASC, p.id DESC") + List findAllOrderByYearDesc(); + + List findByCategory(String category); + + List findByYear(Integer year); +} \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/PublicationService.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/PublicationService.java new file mode 100644 index 0000000..1c460ae --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/PublicationService.java @@ -0,0 +1,33 @@ +package net.shyshkin.study.fullstack.supportportal.backend.service; + +import net.shyshkin.study.fullstack.supportportal.backend.domain.Publication; + +import java.util.List; + +public interface PublicationService { + + Publication addPublication(String title, String authors, Integer year, String journal, + String doi, String category, String abstractText, + String publicationDate, String keywords, Integer displayOrder); + + Publication updatePublication(Long id, String title, String authors, Integer year, + String journal, String doi, String category, + String abstractText, String publicationDate, + String keywords, Integer displayOrder); + + List getActivePublications(); + + Publication getPublicationById(Long id); + + List getAllPublications(); + + List getPublicationsByCategory(String category); + + List getPublicationsByYear(Integer year); + + void deletePublication(Long id); + + Publication toggleActiveStatus(Long id); + + void reorderPublications(List orderedIds); +} \ No newline at end of file diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/impl/PublicationServiceImpl.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/impl/PublicationServiceImpl.java new file mode 100644 index 0000000..2617c18 --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/impl/PublicationServiceImpl.java @@ -0,0 +1,129 @@ +package net.shyshkin.study.fullstack.supportportal.backend.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.shyshkin.study.fullstack.supportportal.backend.domain.Publication; +import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.PublicationNotFoundException; +import net.shyshkin.study.fullstack.supportportal.backend.repository.PublicationRepository; +import net.shyshkin.study.fullstack.supportportal.backend.service.PublicationService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +@Transactional +public class PublicationServiceImpl implements PublicationService { + + private final PublicationRepository publicationRepository; + + @Override + public Publication addPublication(String title, String authors, Integer year, String journal, + String doi, String category, String abstractText, + String publicationDate, String keywords, Integer displayOrder) { + log.info("Adding new publication: {}", title); + + Publication publication = new Publication(); + publication.setTitle(title); + publication.setAuthors(authors); + publication.setYear(year); + publication.setJournal(journal); + publication.setDoi(doi); + publication.setCategory(category); + publication.setAbstractText(abstractText); + publication.setPublicationDate(publicationDate); + publication.setKeywords(keywords); + publication.setDisplayOrder(displayOrder != null ? displayOrder : 0); + publication.setActive(true); + + return publicationRepository.save(publication); + } + + @Override + public Publication updatePublication(Long id, String title, String authors, Integer year, + String journal, String doi, String category, + String abstractText, String publicationDate, + String keywords, Integer displayOrder) { + log.info("Updating publication with id: {}", id); + + Publication publication = getPublicationById(id); + publication.setTitle(title); + publication.setAuthors(authors); + publication.setYear(year); + publication.setJournal(journal); + publication.setDoi(doi); + publication.setCategory(category); + publication.setAbstractText(abstractText); + publication.setPublicationDate(publicationDate); + publication.setKeywords(keywords); + + if (displayOrder != null) { + publication.setDisplayOrder(displayOrder); + } + + return publicationRepository.save(publication); + } + + @Override + @Transactional(readOnly = true) + public List getActivePublications() { + return publicationRepository.findByIsActiveTrueOrderByYearDesc(); + } + + @Override + @Transactional(readOnly = true) + public Publication getPublicationById(Long id) { + return publicationRepository.findById(id) + .orElseThrow(() -> new PublicationNotFoundException("Publication not found with id: " + id)); + } + + @Override + @Transactional(readOnly = true) + public List getAllPublications() { + return publicationRepository.findAllOrderByYearDesc(); + } + + @Override + @Transactional(readOnly = true) + public List getPublicationsByCategory(String category) { + return publicationRepository.findByCategory(category); + } + + @Override + @Transactional(readOnly = true) + public List getPublicationsByYear(Integer year) { + return publicationRepository.findByYear(year); + } + + @Override + public void deletePublication(Long id) { + log.info("Deleting publication with id: {}", id); + + Publication publication = getPublicationById(id); + publicationRepository.delete(publication); + } + + @Override + public Publication toggleActiveStatus(Long id) { + log.info("Toggling active status for publication with id: {}", id); + + Publication publication = getPublicationById(id); + publication.setActive(!publication.isActive()); + + return publicationRepository.save(publication); + } + + @Override + public void reorderPublications(List orderedIds) { + log.info("Reordering {} publications", orderedIds.size()); + + for (int i = 0; i < orderedIds.size(); i++) { + Long id = orderedIds.get(i); + Publication publication = getPublicationById(id); + publication.setDisplayOrder(i); + publicationRepository.save(publication); + } + } +} \ No newline at end of file diff --git a/support-portal-backend/src/main/resources/application.yml b/support-portal-backend/src/main/resources/application.yml index 2a602cd..1d30aa2 100644 --- a/support-portal-backend/src/main/resources/application.yml +++ b/support-portal-backend/src/main/resources/application.yml @@ -51,7 +51,7 @@ file: app: base-url: ${APP_BASE_URL:http://localhost:8080} # Fixed public URLs with correct wildcard patterns - public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications/**,/api/job-applications/resume/**,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active,/api/milestones,/api/milestones/**,/api/testimonials,/api/testimonials/**,/hero/image/**,/hero/active/**,/hero/**,/service-tiles/active,/service-tiles/active/** + public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications/**,/api/job-applications/resume/**,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active,/api/milestones,/api/milestones/**,/api/testimonials,/api/testimonials/**,/hero/image/**,/hero/active/**,/hero/**,/service-tiles/active,/service-tiles/active/**,/publications/active/**,/publications/*/**,/publications/category/**,/publications/year/** cors: allowed-origins: http://localhost:4200,http://localhost:3000,https://maincmc.rootxwire.com,https://dashboard.cmctrauma.com,https://www.dashboard.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com jwt: diff --git a/support-portal-frontend/src/app/admin/admin-routing.module.ts b/support-portal-frontend/src/app/admin/admin-routing.module.ts index 6e71833..9f732b6 100644 --- a/support-portal-frontend/src/app/admin/admin-routing.module.ts +++ b/support-portal-frontend/src/app/admin/admin-routing.module.ts @@ -25,6 +25,7 @@ import { TestimonialFormComponent } from '../component/testimonial/testimonial-f import { TestimonialListComponent } from '../component/testimonial/testimonial-list/testimonial-list.component'; import { HeroImageComponent } from '../component/hero-image/hero-image.component'; import { ServiceTileComponent } from '../component/service-tile/service-tile.component'; +import { PublicationsComponent } from '../component/publications/publications.component'; const routes: Routes = [ { path: '', component: HomeComponent }, @@ -44,6 +45,7 @@ const routes: Routes = [ { path: 'professorManagement', component: ProfessorComponent, canActivate: [AuthenticationGuard] }, { path: 'heroImage', component: HeroImageComponent, canActivate: [AuthenticationGuard] }, { path: 'serviceTiles', component: ServiceTileComponent, canActivate: [AuthenticationGuard] }, + { path: 'publications', component: PublicationsComponent, canActivate: [AuthenticationGuard] }, // ← ADD THIS { path: 'milestone/list', component: MilestoneListComponent, canActivate: [AuthenticationGuard] }, { path: 'milestone/create', component: MilestoneFormComponent, canActivate: [AuthenticationGuard] }, { path: 'milestone/edit/:id', component: MilestoneFormComponent, canActivate: [AuthenticationGuard] }, diff --git a/support-portal-frontend/src/app/admin/admin.module.ts b/support-portal-frontend/src/app/admin/admin.module.ts index ddc4da0..1761e53 100644 --- a/support-portal-frontend/src/app/admin/admin.module.ts +++ b/support-portal-frontend/src/app/admin/admin.module.ts @@ -40,6 +40,7 @@ import { TestimonialFormComponent } from '../component/testimonial/testimonial-f import { TestimonialListComponent } from '../component/testimonial/testimonial-list/testimonial-list.component'; import { HeroImageComponent } from '../component/hero-image/hero-image.component'; import { ServiceTileComponent } from '../component/service-tile/service-tile.component'; +import { PublicationsComponent } from '../component/publications/publications.component'; // import { PagesModule } from '../pages/pages.module'; @@ -72,7 +73,8 @@ import { ServiceTileComponent } from '../component/service-tile/service-tile.com TestimonialFormComponent, TestimonialListComponent, HeroImageComponent, - ServiceTileComponent + ServiceTileComponent, + PublicationsComponent ], imports: [ CommonModule, diff --git a/support-portal-frontend/src/app/app.module.ts b/support-portal-frontend/src/app/app.module.ts index 31b96ca..e93f923 100644 --- a/support-portal-frontend/src/app/app.module.ts +++ b/support-portal-frontend/src/app/app.module.ts @@ -38,6 +38,7 @@ import { TestimonialFormComponent } from './component/testimonial/testimonial-fo import { TestimonialListComponent } from './component/testimonial/testimonial-list/testimonial-list.component'; import { HeroImageComponent } from './component/hero-image/hero-image.component'; import { ServiceTileComponent } from './component/service-tile/service-tile.component'; +import { PublicationsComponent } from './component/publications/publications.component'; // import { PagesModule } from './pages/pages.module'; @@ -47,6 +48,7 @@ import { ServiceTileComponent } from './component/service-tile/service-tile.comp @NgModule({ declarations: [ AppComponent, + //PublicationsComponent, //ServiceTileComponent, //HeroImageComponent, //TestimonialFormComponent, diff --git a/support-portal-frontend/src/app/component/hero-image/hero-image.component.css b/support-portal-frontend/src/app/component/hero-image/hero-image.component.css index d45b977..443f85e 100644 --- a/support-portal-frontend/src/app/component/hero-image/hero-image.component.css +++ b/support-portal-frontend/src/app/component/hero-image/hero-image.component.css @@ -315,7 +315,7 @@ width: 100px; height: 100px; margin: 0 auto 24px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: #3b82f6; border-radius: 50%; display: flex; align-items: center; diff --git a/support-portal-frontend/src/app/component/menu/menu.component.html b/support-portal-frontend/src/app/component/menu/menu.component.html index 97bfb11..6f832f4 100644 --- a/support-portal-frontend/src/app/component/menu/menu.component.html +++ b/support-portal-frontend/src/app/component/menu/menu.component.html @@ -26,8 +26,7 @@