✨ Notifications
This commit is contained in:
		| @@ -9,5 +9,6 @@ type Notification struct { | ||||
| 	Content     string     `json:"content"` | ||||
| 	IsImportant bool       `json:"is_important"` | ||||
| 	ReadAt      *time.Time `json:"read_at"` | ||||
| 	SenderID    *uint      `json:"sender_id"` | ||||
| 	RecipientID uint       `json:"recipient_id"` | ||||
| } | ||||
|   | ||||
| @@ -20,7 +20,6 @@ func getUserinfo(c *fiber.Ctx) error { | ||||
| 		Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}). | ||||
| 		Preload("Profile"). | ||||
| 		Preload("Contacts"). | ||||
| 		Preload("Factors"). | ||||
| 		Preload("Notifications", "read_at IS NULL"). | ||||
| 		First(&data).Error; err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
|   | ||||
							
								
								
									
										59
									
								
								pkg/server/notifications_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								pkg/server/notifications_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/hydrogen/passport/pkg/database" | ||||
| 	"code.smartsheep.studio/hydrogen/passport/pkg/models" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func getNotifications(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	take := c.QueryInt("take", 0) | ||||
| 	offset := c.QueryInt("offset", 0) | ||||
|  | ||||
| 	var count int64 | ||||
| 	var notifications []models.Notification | ||||
| 	if err := database.C. | ||||
| 		Where(&models.Notification{RecipientID: user.ID}). | ||||
| 		Model(&models.Notification{}). | ||||
| 		Count(&count).Error; err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if err := database.C. | ||||
| 		Where(&models.Notification{RecipientID: user.ID}). | ||||
| 		Limit(take). | ||||
| 		Offset(offset). | ||||
| 		Order("read_at desc"). | ||||
| 		Find(¬ifications).Error; err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"count": count, | ||||
| 		"data":  notifications, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func markNotificationRead(c *fiber.Ctx) error { | ||||
| 	user := c.Locals("principal").(models.Account) | ||||
| 	id, _ := c.ParamsInt("notificationId", 0) | ||||
|  | ||||
| 	var data models.Notification | ||||
| 	if err := database.C.Where(&models.Notification{ | ||||
| 		BaseModel:   models.BaseModel{ID: uint(id)}, | ||||
| 		RecipientID: user.ID, | ||||
| 	}).First(&data).Error; err != nil { | ||||
| 		return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	data.ReadAt = lo.ToPtr(time.Now()) | ||||
|  | ||||
| 	if err := database.C.Save(&data).Error; err != nil { | ||||
| 		return fiber.NewError(fiber.StatusInternalServerError, err.Error()) | ||||
| 	} else { | ||||
| 		return c.SendStatus(fiber.StatusOK) | ||||
| 	} | ||||
| } | ||||
| @@ -60,6 +60,9 @@ func NewServer() { | ||||
| 		api.Get("/avatar/:avatarId", getAvatar) | ||||
| 		api.Put("/avatar", auth, setAvatar) | ||||
|  | ||||
| 		api.Get("/notifications", auth, getNotifications) | ||||
| 		api.Put("/notifications/:notificationId/read", auth, markNotificationRead) | ||||
|  | ||||
| 		api.Get("/users/me", auth, getUserinfo) | ||||
| 		api.Put("/users/me", auth, editUserinfo) | ||||
| 		api.Get("/users/me/events", auth, getEvents) | ||||
|   | ||||
| @@ -1,9 +1,11 @@ | ||||
| import { useUserinfo } from "../stores/userinfo.tsx"; | ||||
| import { Show } from "solid-js"; | ||||
| import { getAtk, readProfiles, useUserinfo } from "../stores/userinfo.tsx"; | ||||
| import { createSignal, For, Show } from "solid-js"; | ||||
|  | ||||
| export default function DashboardPage() { | ||||
|   const userinfo = useUserinfo(); | ||||
|  | ||||
|   const [error, setError] = createSignal<string | null>(null); | ||||
|  | ||||
|   function getGreeting() { | ||||
|     const currentHour = new Date().getHours(); | ||||
|  | ||||
| @@ -16,6 +18,19 @@ export default function DashboardPage() { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async function readNotification(item: any) { | ||||
|     const res = await fetch(`/api/notifications/${item.id}/read`, { | ||||
|       method: "PUT", | ||||
|       headers: { Authorization: `Bearer ${getAtk()}` } | ||||
|     }); | ||||
|     if (res.status !== 200) { | ||||
|       setError(await res.text()); | ||||
|     } else { | ||||
|       await readProfiles(); | ||||
|       setError(null); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <div class="max-w-[720px] mx-auto px-5 pt-12"> | ||||
|       <div id="greeting" class="px-5"> | ||||
| @@ -37,17 +52,54 @@ export default function DashboardPage() { | ||||
|             </div> | ||||
|           </div> | ||||
|         </Show> | ||||
|         <Show when={error()}> | ||||
|           <div role="alert" class="alert alert-error mt-5"> | ||||
|             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||
|                  viewBox="0 0 24 24"> | ||||
|               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||
|                     d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||
|             </svg> | ||||
|             <span class="capitalize">{error()}</span> | ||||
|           </div> | ||||
|         </Show> | ||||
|       </div> | ||||
|  | ||||
|       <div class="card shadow-xl mt-5"> | ||||
|         <div class="card-body"> | ||||
|           <h2 class="card-title">Recommendations</h2> | ||||
|           <ol> | ||||
|             <li>1. Turn on the notification of us.</li> | ||||
|             <li>2. Download passport mobile application.</li> | ||||
|             <li>3. Add more factor to keep your account in safe.</li> | ||||
|             <li>4. Subscribe to Project Hydrogen to get following updates.</li> | ||||
|           </ol> | ||||
|           <h2 class="card-title">Notifications</h2> | ||||
|           <div class="bg-base-200 mt-3 mx-[-32px]"> | ||||
|             <Show when={userinfo?.meta?.notifications?.length <= 0}> | ||||
|               <table class="table"> | ||||
|                 <tbody> | ||||
|                 <tr> | ||||
|                   <td class="px-[32px]">You're done! There are no notifications unread for you.</td> | ||||
|                 </tr> | ||||
|                 </tbody> | ||||
|               </table> | ||||
|             </Show> | ||||
|             <Show when={userinfo?.meta?.notifications?.length > 0}> | ||||
|               <table class="table"> | ||||
|                 <tbody> | ||||
|                 <For each={userinfo?.meta?.notifications}> | ||||
|                   {item => | ||||
|                     <tr> | ||||
|                       <td class="px-[32px]"> | ||||
|                         <h2 class="font-bold">{item.subject}</h2> | ||||
|                         <p>{item.content}</p> | ||||
|                         <div class="flex gap-2"> | ||||
|                           <Show when={item.is_important}> | ||||
|                             <span class="font-bold">Important</span> | ||||
|                           </Show> | ||||
|                           <a class="link" onClick={() => readNotification(item)}>Mark as read</a> | ||||
|                         </div> | ||||
|                       </td> | ||||
|                     </tr> | ||||
|                   } | ||||
|                 </For> | ||||
|                 </tbody> | ||||
|               </table> | ||||
|             </Show> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user