Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
67e6f65
docs: notification architecture decisions and mobile context
mohitgauniyal Apr 23, 2026
d113384
fix: update adaptive icon with proper safe zone padding
mohitgauniyal Apr 23, 2026
239398f
feat: MSG91 SMS provider integration
mohitgauniyal Apr 23, 2026
b332aff
Merge pull request #62 from inspirebyte-tech/feature/sms-provider
mohitgauniyal Apr 23, 2026
aee9d3b
fix: make rolePermission seed upsert idempotent
mohitgauniyal Apr 23, 2026
9599777
feat: announcements backend
mohitgauniyal Apr 23, 2026
a08b256
feat: add announcements API service
mohitgauniyal Apr 23, 2026
ec81b82
feat: add AnnouncementsListScreen with filters, pinned rows, and FAB
mohitgauniyal Apr 23, 2026
c207336
feat: add CreateAnnouncementScreen with category chips and image picker
mohitgauniyal Apr 23, 2026
e9bd68e
feat: register AnnouncementsList and CreateAnnouncement routes
mohitgauniyal Apr 23, 2026
89b7fd0
feat: add Announcements as first quick action on dashboard
mohitgauniyal Apr 23, 2026
6f3ac68
feat: add AnnouncementDetailScreen with pin/delete actions
mohitgauniyal Apr 23, 2026
8baf233
fix: wire row tap to AnnouncementDetailScreen
mohitgauniyal Apr 23, 2026
1f7e0c0
feat: register AnnouncementDetail route
mohitgauniyal Apr 23, 2026
7ae14e7
fix: replace Alert.alert with ConfirmSheet for delete confirmation
mohitgauniyal Apr 23, 2026
bd7d565
Merge pull request #63 from inspirebyte-tech/feature/announcements
mohitgauniyal Apr 23, 2026
2cbfecc
docs: announcements endpoints and mobile screens
mohitgauniyal Apr 23, 2026
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
5 changes: 5 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ JWT_SECRET="your-random-secret-here"
PORT=3000
NODE_ENV=development
JWT_REFRESH_SECRET=your-refresh-secret-here

# Add these lines to apps/api/.env.example
MSG91_AUTH_KEY=your_msg91_auth_key
MSG91_SENDER_ID=VAASTIO
MSG91_TEMPLATE_ID=your_template_id
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-- CreateEnum
CREATE TYPE "AnnouncementCategory" AS ENUM ('GENERAL', 'MAINTENANCE', 'MEETING', 'EMERGENCY', 'CELEBRATION');

-- CreateTable
CREATE TABLE "announcements" (
"id" TEXT NOT NULL,
"orgId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"category" "AnnouncementCategory" NOT NULL DEFAULT 'GENERAL',
"isPinned" BOOLEAN NOT NULL DEFAULT false,
"createdBy" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"deletedAt" TIMESTAMP(3),

CONSTRAINT "announcements_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "announcement_images" (
"id" TEXT NOT NULL,
"announcementId" TEXT NOT NULL,
"imageUrl" TEXT NOT NULL,

CONSTRAINT "announcement_images_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "announcements_orgId_idx" ON "announcements"("orgId");

-- CreateIndex
CREATE INDEX "announcements_orgId_isPinned_idx" ON "announcements"("orgId", "isPinned");

-- AddForeignKey
ALTER TABLE "announcements" ADD CONSTRAINT "announcements_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "organizations"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "announcements" ADD CONSTRAINT "announcements_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "announcement_images" ADD CONSTRAINT "announcement_images_announcementId_fkey" FOREIGN KEY ("announcementId") REFERENCES "announcements"("id") ON DELETE CASCADE ON UPDATE CASCADE;
41 changes: 41 additions & 0 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ model User {
deviceTokens DeviceToken[]
complaintsRaised Complaint[] @relation("ComplaintRaiser")
complaintsResolved Complaint[] @relation("ComplaintResolver")
announcementsCreated Announcement[] @relation("AnnouncementCreator")

@@map("users")
}
Expand Down Expand Up @@ -73,6 +74,7 @@ model Organization {
auditLogs AuditLog[]
invitations Invitation[]
complaints Complaint[]
announcements Announcement[]

@@map("organizations")
}
Expand Down Expand Up @@ -354,6 +356,37 @@ model ComplaintImage {
@@map("complaint_images")
}

model Announcement {
id String @id @default(uuid())
orgId String
title String
body String
category AnnouncementCategory @default(GENERAL)
isPinned Boolean @default(false)
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?

org Organization @relation(fields: [orgId], references: [id])
creator User @relation("AnnouncementCreator", fields: [createdBy], references: [id])
images AnnouncementImage[]

@@index([orgId])
@@index([orgId, isPinned])
@@map("announcements")
}

model AnnouncementImage {
id String @id @default(uuid())
announcementId String
imageUrl String

announcement Announcement @relation(fields: [announcementId], references: [id], onDelete: Cascade)

@@map("announcement_images")
}

enum ComplaintCategory {
WATER_SUPPLY
ELECTRICITY
Expand Down Expand Up @@ -385,4 +418,12 @@ enum ComplaintStatus {
OPEN
RESOLVED
REJECTED
}

enum AnnouncementCategory {
GENERAL
MAINTENANCE
MEETING
EMERGENCY
CELEBRATION
}
40 changes: 30 additions & 10 deletions apps/api/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ async function main() {
{ name: 'complaint.resolve_any', module: 'complaints', description: 'Resolve any complaint' },
{ name: 'complaint.reject', module: 'complaints', description: 'Reject a complaint with reason' },

// Announcement
{ name: 'announcement.create', module: 'announcements', description: 'Create announcements' },
{ name: 'announcement.delete', module: 'announcements', description: 'Delete announcements' },
{ name: 'announcement.pin', module: 'announcements', description: 'Pin announcements' },
{ name: 'announcement.view', module: 'announcements', description: 'View announcements' },

// Units
{ name: 'unit.assign', module: 'units', description: 'Assign ownership and occupancy to units' },
{ name: 'unit.view_all', module: 'units', description: 'View all units and assignments in society' },
Expand Down Expand Up @@ -117,7 +123,11 @@ async function main() {
'visitor.view_live', 'visitor.view_emergency',
'emergency.declare', 'emergency.view',
'role.create', 'role.assign', 'role.view',
'unit.assign', 'unit.view_all'
'unit.assign', 'unit.view_all',
'announcement.create',
'announcement.delete',
'announcement.pin',
'announcement.view'
],

Admin: [
Expand All @@ -135,7 +145,11 @@ async function main() {
'asset.create', 'asset.book', 'asset.view', 'asset.manage_booking',
'role.create', 'role.assign', 'role.view',
'complaint.view_all', 'complaint.resolve_any', 'complaint.reject',
'unit.assign', 'unit.view_all'
'unit.assign', 'unit.view_all',
'announcement.create',
'announcement.delete',
'announcement.pin',
'announcement.view'
],

Resident: [
Expand All @@ -148,7 +162,7 @@ async function main() {
'emergency.declare', 'emergency.view',
'asset.book', 'asset.view',
'co_resident.invite',
'unit.view_own'
'unit.view_own', 'announcement.view'
],

'Co-resident': [
Expand All @@ -160,13 +174,15 @@ async function main() {
'poll.vote', 'poll.view',
'emergency.declare', 'emergency.view',
'asset.book', 'asset.view',
'unit.view_own'
'unit.view_own',
'announcement.view'
],

Gatekeeper: [
'society.view',
'visitor.log', 'visitor.view_live',
'emergency.declare', 'emergency.view'
'emergency.declare', 'emergency.view',
'announcement.view'
]
}

Expand Down Expand Up @@ -202,11 +218,15 @@ async function main() {
where: { name: permName }
})
if (permission) {
await prisma.rolePermission.create({
data: {
roleId: role.id,
permissionId: permission.id
}
await prisma.rolePermission.upsert({
where: {
roleId_permissionId: {
roleId: role.id,
permissionId: permission.id
}
},
update: {},
create: { roleId: role.id, permissionId: permission.id }
})
} else {
console.warn(`Permission not found: ${permName}`)
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import invitationsRouter from './routes/invitations'
import membersRouter from './routes/members'
import deviceTokensRouter from './routes/deviceTokens'
import complaintsRouter from './routes/complaints'
import announcementsRouter from './routes/announcements'
import unitsRouter from './routes/units'
import { enforceTenantContext } from './middleware/tenantContext'
import { errorHandler } from './middleware/error'
Expand All @@ -27,6 +28,7 @@ app.use('/api/societies', invitationsRouter)
app.use('/api/societies', membersRouter)
app.use('/api/auth', deviceTokensRouter)
app.use('/api/societies', complaintsRouter)
app.use('/api/societies', announcementsRouter)
app.use('/api/societies', unitsRouter)

// Initialize notification dispatcher
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/notifications/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const notificationRules: Partial<Record<string, NotificationRule>> = {
payload: (d) => ({
title: d.societyName,
body: d.title,
priority: 'high' as const,
priority: d.category === 'EMERGENCY' ? 'high' : 'default',
data: {
screen: 'Announcements',
orgId: d.orgId,
Expand Down
Loading
Loading