Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.exampe
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ DB_NAME=
DB_PORT=
JWT_SECRET=
APP_PASSWORD=
SENDER_EMAIL=
SENDER_EMAIL=
FRONTEND_URL=
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Deploy to College Server
name: Push to production

on:
push:
Expand Down
24 changes: 24 additions & 0 deletions app/src/pages/admin/AdminPostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Info,
Check,
Clock,
Users,
} from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';

Expand Down Expand Up @@ -77,6 +78,7 @@ interface FacultyPost {
updated_at: string;
comments: Comment[] | null;
status_audit_logs?: StatusAudit[] | null;
people_in_thread?: string[] | null;
}

interface WardenPost {
Expand All @@ -94,6 +96,7 @@ interface WardenPost {
updated_at: string;
comments: Comment[] | null;
status_audit_logs?: StatusAudit[] | null;
people_in_thread?: string[] | null;
}

interface CentreHeadPost {
Expand All @@ -110,6 +113,7 @@ interface CentreHeadPost {
updated_at: string;
comments: Comment[] | null;
status_audit_logs?: StatusAudit[] | null;
people_in_thread?: string[] | null;
}

type Post = FacultyPost | WardenPost | CentreHeadPost;
Expand Down Expand Up @@ -744,6 +748,26 @@ export function AdminPostView() {
<Detail label="Last updated" value={formatDate(post.updated_at)} />
</dl>

{/* People in conversation thread */}
<h2 className="text-xs font-bold uppercase tracking-wider text-gray-400 mt-8 mb-4 flex items-center gap-1.5">
<Users className="w-3.5 h-3.5" /> People in conversation thread
</h2>
<div className="flex flex-wrap gap-2 mb-2">
{post.people_in_thread && post.people_in_thread.length > 0 ? (
post.people_in_thread.map((email) => (
<span
key={email}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-700 shadow-sm"
>
<span className="w-1.5 h-1.5 rounded-full bg-indigo-500" />
{email}
</span>
))
) : (
<span className="text-xs text-gray-400 italic">No one in thread yet</span>
)}
</div>

{/* ── Timeline & Comments ── */}
<div className="mt-10 pt-8 border-t border-gray-200">
<h2 className="text-sm font-bold text-gray-900 flex items-center gap-2 mb-5">
Expand Down
27 changes: 26 additions & 1 deletion app/src/pages/post/PostView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useParams, useNavigate, Link } from 'react-router-dom';
import {
Zap, Hammer, Trash2, Pencil, X, Check, Calendar, MapPin, BedDouble,
MessageSquare, Wrench, ArrowLeft, Send, AlertCircle,
Clock,
Clock, Users,
} from 'lucide-react';
import { MainLayout } from '../../components/layout/MainLayout';
import { POST_PLACES } from '../../constants/models';
Expand Down Expand Up @@ -37,6 +37,7 @@ interface ComplaintPost {
updated_at?: string;
comments?: ComplaintComment[] | null;
status_audit_logs?: StatusAudit[] | null;
people_in_thread?: string[] | null;
}

interface EditForm {
Expand Down Expand Up @@ -458,6 +459,30 @@ export function PostView() {
</div>
</div>

{/* People in conversation thread */}
{!isEditing && (
<div>
<h2 className="text-xs font-bold uppercase tracking-wider text-gray-400 mb-3 flex items-center gap-1.5">
<Users className="w-3.5 h-3.5" /> People in conversation thread
</h2>
<div className="flex flex-wrap gap-2">
{post.people_in_thread && post.people_in_thread.length > 0 ? (
post.people_in_thread.map((email) => (
<span
key={email}
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg border border-gray-200 bg-white text-xs font-medium text-gray-700 shadow-sm"
>
<span className="w-1.5 h-1.5 rounded-full bg-indigo-500" />
{email}
</span>
))
) : (
<span className="text-xs text-gray-400 italic">No one in thread yet</span>
)}
</div>
</div>
)}

{/* Comments Section */}
{!isEditing && (
<div className="space-y-6">
Expand Down
62 changes: 62 additions & 0 deletions handlers/admin_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package handlers

import (
"errors"
"fmt"
"log"
"strconv"
"time"

"github.com/ayush00git/cms-web/helpers"
"github.com/ayush00git/cms-web/middleware"
"github.com/ayush00git/cms-web/models"
"github.com/ayush00git/cms-web/services"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
Expand Down Expand Up @@ -102,6 +106,64 @@ func (h *AdminHandler) AdminPostComment(c *gin.Context) {
return
}

// update the PeopleInThread field for post
switch p := postModel.(type) {
case *models.FacultyPost:
// add person to conversation thread and send mail to all people in thread
p.PeopleInThread = helpers.AppendUnique(p.PeopleInThread, admin.Email)
result := h.DB.Save(p)
if result.Error != nil {
c.JSON(500, gin.H{"error": "failed updating the conversation thread"})
return
}
go func() {
frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "faculty", p.ID)

if err := services.SendMailToPeopleInThread(p.PeopleInThread, admin.Email, postURL); err != nil {
log.Printf("failed sending notification emails for post #%d: %v", p.ID, err)
return
}
log.Printf("notification emails sent for post #%d", p.ID)
}()

case *models.WardenPost:
p.PeopleInThread = helpers.AppendUnique(p.PeopleInThread, admin.Email)
result := h.DB.Save(p)
if result.Error != nil {
c.JSON(500, gin.H{"error": "failed updating the conversation thread"})
return
}
go func() {
frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "warden", p.ID)

if err := services.SendMailToPeopleInThread(p.PeopleInThread, admin.Email, postURL); err != nil {
log.Printf("failed sending notification emails for post #%d: %v", p.ID, err)
return
}
log.Printf("notification emails sent for post #%d", p.ID)
}()

case *models.CentreheadPost:
p.PeopleInThread = helpers.AppendUnique(p.PeopleInThread, admin.Email)
result := h.DB.Save(p)
if result.Error != nil {
c.JSON(500, gin.H{"error": "failed updating the conversation thread"})
return
}
go func() {
frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, "centrehead", p.ID)

if err := services.SendMailToPeopleInThread(p.PeopleInThread, admin.Email, postURL); err != nil {
log.Printf("failed sending notification emails for post #%d: %v", p.ID, err)
return
}
log.Printf("notification emails sent for post #%d", p.ID)
}()
}

doc := models.Comment{
CommentableID: uint(postID),
CommentableType: postType,
Expand Down
26 changes: 23 additions & 3 deletions handlers/centrehead_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (h *PostHandler) CentreheadPost(c *gin.Context) {
TypeOfPost: models.PostType(inputs.TypeOfPost),
Title: inputs.Title,
Description: inputs.Description,
PeopleInThread: []string{head.Email},
StatusAuditLogs: []models.StatusAudit{
{
Event: string(PendingXEN),
Expand Down Expand Up @@ -92,16 +93,23 @@ func (h *PostHandler) CentreheadPost(c *gin.Context) {
}
// through type of post send the mail to the corresponding civil/electrical XEN
var xen models.Admin

result := h.DB.Where("position = ?", position).Take(&xen)
if result.Error != nil {
log.Printf("failed to send XEN mail for post %d", post.ID)
log.Printf("failed fetching user at the moment %v", result.Error)
return
}
// send mail to that user
if err := services.SendPostMailToAdmins(xen.Email, postURL); err != nil {
log.Printf("failed to send XEN mail for post %d: %v", post.ID, err)
}
}()
// append xen's email to the post
post.PeopleInThread = append(post.PeopleInThread, xen.Email)
result = h.DB.Model(&post).Updates(post)
if result.Error != nil {
log.Printf("failed adding xen to the thread")
}
} ()

c.JSON(201, gin.H{"success": "post submitted successfully", "post": post})
}
Expand Down Expand Up @@ -278,7 +286,7 @@ func (h *PostHandler) CentreheadPostComment(c *gin.Context) {
// bind the input
var inputs CommentType
if err := c.ShouldBindJSON(&inputs); err != nil {
c.JSON(401, gin.H{"error": "invalid request body"})
c.JSON(400, gin.H{"error": "invalid request body"})
return
}

Expand All @@ -298,5 +306,17 @@ func (h *PostHandler) CentreheadPostComment(c *gin.Context) {
return
}

// send email notification to people involved in the conversation
go func(post models.CentreheadPost, headEmail string) {
frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/centre-head/post/%d`, frontendURL, post.ID)

if err := services.SendMailToPeopleInThread(post.PeopleInThread, headEmail, postURL); err != nil {
log.Printf("failed sending notification emails for post #%d: %v", post.ID, err)
return
}
log.Printf("notification emails sent for post #%d", post.ID)
}(post, head.Email)

c.JSON(201, gin.H{"success": "comment posted!"})
}
40 changes: 31 additions & 9 deletions handlers/faculty_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,23 @@ type PostHandler struct {
DB *gorm.DB
}

// FacultyPostEditType
type FacultyPostEditType struct {
// FacultyPostType
type FacultyPostType struct {
Place string `json:"place"`
TypeOfPost string `json:"type_of_post"`
Title string `json:"title"`
Description string `json:"description"`
UpdatedAt time.Time `json:"updated_at"`
}

// FacultyPostType
type FacultyPostType struct {
// FacultyPostEditType
type FacultyPostEditType struct {
Place string `json:"place"`
TypeOfPost string `json:"type_of_post"`
Title string `json:"title"`
Description string `json:"description"`
UpdatedAt time.Time `json:"updated_at"`
}


// FacultyPost registers the post of faculty members.
// forwards the post to the associated XEN.
func (h *PostHandler) FacultyPost(c *gin.Context) {
Expand Down Expand Up @@ -72,6 +73,7 @@ func (h *PostHandler) FacultyPost(c *gin.Context) {
TypeOfPost: models.PostType(inputs.TypeOfPost),
Title: inputs.Title,
Description: inputs.Description,
PeopleInThread: []string{faculty.Email},
StatusAuditLogs: []models.StatusAudit{
{
Event: string(PendingXEN),
Expand All @@ -90,6 +92,7 @@ func (h *PostHandler) FacultyPost(c *gin.Context) {

frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/admin/posts/%s/%d`, frontendURL, faculty.Role, post.ID)
// forward the post creation update to xen
go func() {
var position models.PositionType
if post.TypeOfPost == "Civil" {
Expand All @@ -99,16 +102,23 @@ func (h *PostHandler) FacultyPost(c *gin.Context) {
}
// through type of post send the mail to the corresponding civil/electrical XEN
var xen models.Admin

result := h.DB.Where("position = ?", position).Take(&xen)
if result.Error != nil {
log.Printf("failed to send XEN mail for post %d", post.ID)
log.Printf("failed fetching user at the moment %v", result.Error)
return
}
// send mail to that user
if err := services.SendPostMailToAdmins(xen.Email, postURL); err != nil {
log.Printf("failed to send XEN mail for post %d: %v", post.ID, err)
}
}()
// append xen's email to the post
post.PeopleInThread = append(post.PeopleInThread, xen.Email)
result = h.DB.Model(&post).Updates(post)
if result.Error != nil {
log.Printf("failed adding xen to the thread")
}
} ()

c.JSON(201, gin.H{"success": "post submitted successfully", "post": post})
}
Expand Down Expand Up @@ -289,7 +299,7 @@ func (h *PostHandler) FacultyPostComment(c *gin.Context) {
// bind input to json
var inputs CommentType
if err := c.ShouldBindJSON(&inputs); err != nil {
c.JSON(401, gin.H{"error": "invalid request body"})
c.JSON(400, gin.H{"error": "invalid request body"})
return
}

Expand All @@ -309,5 +319,17 @@ func (h *PostHandler) FacultyPostComment(c *gin.Context) {
return
}

// send email notification to people involved in the conversation
go func(post models.FacultyPost, facultyEmail string) {
frontendURL := helpers.GetEnvWithDefault("FRONTEND_URL", "http://localhost:5173")
postURL := fmt.Sprintf(`%s/faculty/post/%d`, frontendURL, post.ID)

if err := services.SendMailToPeopleInThread(post.PeopleInThread, facultyEmail, postURL); err != nil {
log.Printf("failed sending notification emails for post #%d: %v", post.ID, err)
return
}
log.Printf("notification emails sent for post #%d", post.ID)
}(post, faculty.Email)

c.JSON(201, gin.H{"success": "comment posted!"})
}
Loading
Loading