Fix notifications: add archived_at to model/queries, archived tab returns only archived, add bottom nav to notifications screen
This commit is contained in:
parent
46566f394b
commit
8186e9e71c
|
|
@ -41,6 +41,7 @@ type Notification struct {
|
|||
CommentID *uuid.UUID `json:"comment_id,omitempty" db:"comment_id"`
|
||||
IsRead bool `json:"is_read" db:"is_read"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
ArchivedAt *time.Time `json:"archived_at,omitempty" db:"archived_at"`
|
||||
Metadata json.RawMessage `json:"metadata" db:"metadata"`
|
||||
GroupKey *string `json:"group_key,omitempty" db:"group_key"`
|
||||
Priority string `json:"priority" db:"priority"`
|
||||
|
|
|
|||
|
|
@ -107,12 +107,12 @@ func (r *NotificationRepository) CreateNotification(ctx context.Context, notif *
|
|||
func (r *NotificationRepository) GetNotifications(ctx context.Context, userID string, limit, offset int, includeArchived bool) ([]models.Notification, error) {
|
||||
whereClause := "WHERE n.user_id = $1::uuid AND n.archived_at IS NULL"
|
||||
if includeArchived {
|
||||
whereClause = "WHERE n.user_id = $1::uuid"
|
||||
whereClause = "WHERE n.user_id = $1::uuid AND n.archived_at IS NOT NULL"
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
n.id, n.user_id, n.type, n.actor_id, n.post_id, n.comment_id, n.is_read, n.created_at, n.metadata,
|
||||
n.id, n.user_id, n.type, n.actor_id, n.post_id, n.comment_id, n.is_read, n.created_at, n.archived_at, n.metadata,
|
||||
COALESCE(n.group_key, '') as group_key,
|
||||
COALESCE(n.priority, 'normal') as priority,
|
||||
pr.handle, pr.display_name, COALESCE(pr.avatar_url, ''),
|
||||
|
|
@ -138,7 +138,7 @@ func (r *NotificationRepository) GetNotifications(ctx context.Context, userID st
|
|||
var postImageURL *string
|
||||
var postBody *string
|
||||
err := rows.Scan(
|
||||
&n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.Metadata,
|
||||
&n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.ArchivedAt, &n.Metadata,
|
||||
&groupKey, &n.Priority,
|
||||
&n.ActorHandle, &n.ActorDisplayName, &n.ActorAvatarURL,
|
||||
&postImageURL, &postBody,
|
||||
|
|
@ -160,7 +160,7 @@ func (r *NotificationRepository) GetNotifications(ctx context.Context, userID st
|
|||
func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, userID string, limit, offset int, includeArchived bool) ([]models.Notification, error) {
|
||||
whereClause := "WHERE n.user_id = $1::uuid AND n.archived_at IS NULL"
|
||||
if includeArchived {
|
||||
whereClause = "WHERE n.user_id = $1::uuid"
|
||||
whereClause = "WHERE n.user_id = $1::uuid AND n.archived_at IS NOT NULL"
|
||||
}
|
||||
|
||||
query := `
|
||||
|
|
@ -180,7 +180,7 @@ func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, us
|
|||
` + whereClause + `
|
||||
)
|
||||
SELECT
|
||||
id, user_id, type, actor_id, post_id, comment_id, is_read, created_at, metadata,
|
||||
id, user_id, type, actor_id, post_id, comment_id, is_read, created_at, archived_at, metadata,
|
||||
COALESCE(group_key, '') as group_key,
|
||||
COALESCE(priority, 'normal') as priority,
|
||||
actor_handle, actor_display_name, actor_avatar_url,
|
||||
|
|
@ -203,7 +203,7 @@ func (r *NotificationRepository) GetGroupedNotifications(ctx context.Context, us
|
|||
var n models.Notification
|
||||
var groupKey string
|
||||
err := rows.Scan(
|
||||
&n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.Metadata,
|
||||
&n.ID, &n.UserID, &n.Type, &n.ActorID, &n.PostID, &n.CommentID, &n.IsRead, &n.CreatedAt, &n.ArchivedAt, &n.Metadata,
|
||||
&groupKey, &n.Priority,
|
||||
&n.ActorHandle, &n.ActorDisplayName, &n.ActorAvatarURL,
|
||||
&n.PostImageURL, &n.PostBody,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import '../../widgets/app_scaffold.dart';
|
|||
import '../../widgets/media/signed_media_image.dart';
|
||||
import '../profile/viewable_profile_screen.dart';
|
||||
import '../post/post_detail_screen.dart';
|
||||
import '../search/search_screen.dart';
|
||||
import '../discover/discover_screen.dart';
|
||||
import '../secure_chat/secure_chat_full_screen.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../services/notification_service.dart';
|
||||
|
||||
|
|
@ -51,6 +54,7 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
void dispose() {
|
||||
_authSub?.cancel();
|
||||
_tabController?.dispose();
|
||||
NotificationService.instance.refreshBadge();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
|
@ -108,28 +112,18 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
);
|
||||
|
||||
if (mounted) {
|
||||
// If showing Active, filter out anything archived
|
||||
final filtered = !showArchived
|
||||
? notifications
|
||||
.where((item) =>
|
||||
item.archivedAt == null &&
|
||||
!_locallyArchivedIds.contains(item.id))
|
||||
.toList()
|
||||
// If showing Archived, only show archived items
|
||||
: notifications
|
||||
.where((item) =>
|
||||
item.archivedAt != null ||
|
||||
_locallyArchivedIds.contains(item.id))
|
||||
// Backend handles active vs archived filtering
|
||||
final filtered = notifications
|
||||
.where((item) => !_locallyArchivedIds.contains(item.id))
|
||||
.toList();
|
||||
|
||||
final fetchedCount = notifications.length;
|
||||
setState(() {
|
||||
if (refresh) {
|
||||
_notifications = filtered;
|
||||
} else {
|
||||
_notifications.addAll(filtered);
|
||||
}
|
||||
_hasMore = fetchedCount == 20;
|
||||
_hasMore = notifications.length == 20;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -358,6 +352,25 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
void _navigateHome() {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void _navigateSearch() {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(builder: (_) => const DiscoverScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateChat() {
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const SecureChatFullScreen(),
|
||||
fullscreenDialog: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final canArchiveAll = _activeTabIndex == 0 && _notifications.isNotEmpty;
|
||||
|
|
@ -365,11 +378,8 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: AppScaffold(
|
||||
title: 'Notifications',
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: 'Activity',
|
||||
leading: const SizedBox.shrink(),
|
||||
actions: [
|
||||
if (canArchiveAll)
|
||||
TextButton(
|
||||
|
|
@ -400,6 +410,7 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
Tab(text: 'Archived'),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: _buildBottomNav(),
|
||||
body: _error != null
|
||||
? _ErrorState(
|
||||
message: _error!,
|
||||
|
|
@ -414,7 +425,6 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == _notifications.length) {
|
||||
// Load more indicator
|
||||
if (!_isLoading) {
|
||||
_loadNotifications();
|
||||
}
|
||||
|
|
@ -428,7 +438,6 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
|
||||
final notification = _notifications[index];
|
||||
if (_activeTabIndex == 0) {
|
||||
// Swipe to archive in Active tab
|
||||
return Dismissible(
|
||||
key: Key('notif_${notification.id}'),
|
||||
direction: DismissDirection.endToStart,
|
||||
|
|
@ -466,7 +475,6 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
// No swipe to archive in Archived tab
|
||||
return _NotificationItem(
|
||||
notification: notification,
|
||||
onTap: notification.type == NotificationType.follow_request
|
||||
|
|
@ -480,6 +488,86 @@ class _NotificationsScreenState extends ConsumerState<NotificationsScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomNav() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.scaffoldBg,
|
||||
border: Border(
|
||||
top: BorderSide(
|
||||
color: AppTheme.egyptianBlue.withOpacity(0.1),
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildNavItem(
|
||||
icon: Icons.home_outlined,
|
||||
label: 'Home',
|
||||
onTap: _navigateHome,
|
||||
),
|
||||
_buildNavItem(
|
||||
icon: Icons.search,
|
||||
label: 'Discover',
|
||||
onTap: _navigateSearch,
|
||||
),
|
||||
_buildNavItem(
|
||||
icon: Icons.notifications,
|
||||
label: 'Activity',
|
||||
isActive: true,
|
||||
onTap: () {},
|
||||
),
|
||||
_buildNavItem(
|
||||
icon: Icons.chat_bubble_outline,
|
||||
label: 'Chat',
|
||||
onTap: _navigateChat,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNavItem({
|
||||
required IconData icon,
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
bool isActive = false,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
color: isActive ? AppTheme.navyBlue : Colors.grey,
|
||||
size: 26,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: isActive ? AppTheme.navyBlue : Colors.grey,
|
||||
fontWeight: isActive ? FontWeight.w600 : FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Individual notification item widget
|
||||
|
|
|
|||
|
|
@ -1,311 +1,229 @@
|
|||
# Sojorn Development TODO List
|
||||
# Sojorn Development TODO
|
||||
|
||||
**Last Updated**: January 30, 2026
|
||||
**Status**: Ready for 100% Go Backend Migration
|
||||
**Estimated Effort**: ~12 hours total
|
||||
**Last Updated**: February 6, 2026
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **High Priority Tasks** (Critical for 100% Go Backend)
|
||||
## 🎯 High Priority — Feature Work
|
||||
|
||||
### **1. Implement Beacon Voting Endpoints** ⚡
|
||||
**Location**: `go-backend/internal/handlers/post_handler.go`
|
||||
**Status**: Not Started
|
||||
**Effort**: 2 hours
|
||||
### 1. Finalize AI Moderation — Image & Video
|
||||
**Status**: In Progress
|
||||
Text moderation is live (OpenAI Moderation API). Image and video moderation not yet implemented.
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Add `VouchBeacon` handler method
|
||||
- [ ] Add `ReportBeacon` handler method
|
||||
- [ ] Add `RemoveBeaconVote` handler method
|
||||
- [ ] Implement database operations for beacon votes
|
||||
- [ ] Add proper error handling and validation
|
||||
- [ ] Add image moderation to post creation flow (send image to OpenAI or Vision API)
|
||||
- [ ] Add video moderation via thumbnail extraction (grab first frame or key frame)
|
||||
- [ ] Run extracted thumbnail through same image moderation pipeline
|
||||
- [ ] Flag content that exceeds thresholds into `moderation_flags` table
|
||||
- [ ] Wire into existing Three Poisons scoring (Hate, Greed, Delusion)
|
||||
- [ ] Add admin queue visibility for image/video flags
|
||||
|
||||
**Implementation Details:**
|
||||
```go
|
||||
func (h *PostHandler) VouchBeacon(c *gin.Context) {
|
||||
// Get user ID from context
|
||||
// Get beacon ID from params
|
||||
// Check if user already voted
|
||||
// Add vote to database
|
||||
// Update beacon confidence score
|
||||
// Return success response
|
||||
}
|
||||
```
|
||||
|
||||
### **2. Add Beacon Voting Routes** ⚡
|
||||
**Location**: `go-backend/cmd/api/main.go`
|
||||
**Status**: Not Started
|
||||
**Effort**: 30 minutes
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Add `POST /beacons/:id/vouch` route
|
||||
- [ ] Add `POST /beacons/:id/report` route
|
||||
- [ ] Add `DELETE /beacons/:id/vouch` route
|
||||
- [ ] Ensure proper middleware (auth, rate limiting)
|
||||
|
||||
**Routes to Add:**
|
||||
```go
|
||||
authorized.POST("/beacons/:id/vouch", postHandler.VouchBeacon)
|
||||
authorized.POST("/beacons/:id/report", postHandler.ReportBeacon)
|
||||
authorized.DELETE("/beacons/:id/vouch", postHandler.RemoveBeaconVote)
|
||||
```
|
||||
|
||||
### **3. Update Flutter Beacon API** ⚡
|
||||
**Location**: `sojorn_app/lib/services/api_service.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Replace empty `vouchBeacon()` method
|
||||
- [ ] Replace empty `reportBeacon()` method
|
||||
- [ ] Replace empty `removeBeaconVote()` method
|
||||
- [ ] Add proper error handling
|
||||
- [ ] Update method signatures to match Go API
|
||||
|
||||
**Current State:**
|
||||
```dart
|
||||
// These methods are empty stubs that need implementation:
|
||||
Future<void> vouchBeacon(String beaconId) async {
|
||||
// Migrate to Go API - EMPTY STUB
|
||||
}
|
||||
|
||||
Future<void> reportBeacon(String beaconId) async {
|
||||
// Migrate to Go API - EMPTY STUB
|
||||
}
|
||||
|
||||
Future<void> removeBeaconVote(String beaconId) async {
|
||||
// Migrate to Go API - EMPTY STUB
|
||||
}
|
||||
```
|
||||
**Backend**: `go-backend/internal/handlers/post_handler.go` (CreatePost flow)
|
||||
**Key decision**: Use OpenAI Vision API for images, ffmpeg thumbnail extraction for video on the server side
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ **Medium Priority Tasks** (Cleanup & Technical Debt)
|
||||
### 2. Quips — Complete Video Recorder & Editor Overhaul
|
||||
**Status**: Needs major work
|
||||
Current recorder is basic. Goal: TikTok/Instagram-level recording and editing experience.
|
||||
|
||||
### **4. Remove Supabase Function Proxy**
|
||||
**Location**: `go-backend/internal/handlers/function_proxy.go`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
- [ ] Multi-segment recording with pause/resume
|
||||
- [ ] Speed control (0.5x, 1x, 2x, 3x)
|
||||
- [ ] Filters and effects (color grading, beauty mode)
|
||||
- [ ] Text overlays with timing and positioning
|
||||
- [ ] Music/audio overlay from library or device
|
||||
- [ ] Trim and reorder clips
|
||||
- [ ] Transitions between segments
|
||||
- [ ] Preview before posting
|
||||
- [ ] Progress indicator during upload
|
||||
- [ ] Thumbnail selection for posted quip
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Delete `function_proxy.go` file
|
||||
- [ ] Remove function proxy handler instantiation
|
||||
- [ ] Remove `/functions/:name` route from `main.go`
|
||||
- [ ] Remove related environment variables
|
||||
- [ ] Test that no functionality is broken
|
||||
|
||||
**Files to Remove:**
|
||||
- `go-backend/internal/handlers/function_proxy.go`
|
||||
- `go-backend/cmd/supabase-migrate/` (entire directory)
|
||||
|
||||
### **5. Clean Up Supabase Dependencies**
|
||||
**Location**: Multiple files
|
||||
**Status**: Not Started
|
||||
**Effort**: 2 hours
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Remove `SupabaseID` field from `internal/models/user.go`
|
||||
- [ ] Remove Supabase environment variables from `.env.example`
|
||||
- [ ] Update configuration struct in `internal/config/config.go`
|
||||
- [ ] Remove any remaining Supabase imports
|
||||
- [ ] Update middleware comments that reference Supabase
|
||||
|
||||
**Environment Variables to Remove:**
|
||||
```bash
|
||||
# SUPABASE_URL
|
||||
# SUPABASE_KEY
|
||||
# SUPABASE_SERVICE_ROLE_KEY
|
||||
```
|
||||
|
||||
### **6. Update Outdated TODO Comments**
|
||||
**Location**: `sojorn_app/lib/services/api_service.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 30 minutes
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Remove comment "Beacon voting still Supabase RPC or migrate?"
|
||||
- [ ] Remove comment "Summary didn't mention beacon voting endpoint"
|
||||
- [ ] Update any other misleading Supabase references
|
||||
- [ ] Ensure all comments reflect current Go backend state
|
||||
|
||||
### **7. End-to-End Testing**
|
||||
**Location**: Entire application
|
||||
**Status**: Not Started
|
||||
**Effort**: 2 hours
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Test beacon voting flow end-to-end
|
||||
- [ ] Verify all core features work without Supabase
|
||||
- [ ] Test media uploads to R2
|
||||
- [ ] Test E2EE chat functionality
|
||||
- [ ] Test push notifications
|
||||
- [ ] Performance testing
|
||||
- [ ] Security verification
|
||||
**Frontend**: `sojorn_app/lib/screens/quips/create/`
|
||||
**Packages to evaluate**: `camera`, `ffmpeg_kit_flutter`, `video_editor`
|
||||
|
||||
---
|
||||
|
||||
## 🔧 **Low Priority Tasks** (UI Polish & Code Cleanup)
|
||||
### 3. Beacon Page Overhaul — Local Safety & Social Awareness
|
||||
**Status**: Basic beacon system exists (post, vouch, report). Needs full redesign.
|
||||
Vision: Citizen + Nextdoor but focused on social awareness over fear-mongering.
|
||||
|
||||
### **8. Fix Video Comments TODO**
|
||||
**Location**: `sojorn_app/lib/widgets/video_comments_sheet.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
- [ ] Redesign beacon feed as a local safety dashboard
|
||||
- [ ] Map view with clustered pins (incidents, community alerts, mutual aid)
|
||||
- [ ] Beacon categories: Safety Alert, Community Need, Lost & Found, Event, Mutual Aid
|
||||
- [ ] Verified/official source badges for local orgs
|
||||
- [ ] "How to help" action items on each beacon (donate, volunteer, share)
|
||||
- [ ] Tone guidelines — auto-moderate fear-bait and rage-bait language
|
||||
- [ ] Neighborhood/radius filtering
|
||||
- [ ] Push notifications for nearby beacons (opt-in)
|
||||
- [ ] Confidence scoring visible to users (vouch/report ratio)
|
||||
- [ ] Resolution status (active → resolved → archived)
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Replace simulated API call with real `publishComment()` call
|
||||
- [ ] Remove "TODO: Implement actual comment posting" comment
|
||||
- [ ] Test comment posting functionality
|
||||
- [ ] Ensure proper error handling
|
||||
|
||||
**Current State:**
|
||||
```dart
|
||||
// TODO: Implement actual comment posting
|
||||
await Future.delayed(const Duration(seconds: 1)); // Simulate API call
|
||||
```
|
||||
|
||||
**Should Become:**
|
||||
```dart
|
||||
await ApiService.instance.publishComment(postId: widget.postId, body: comment);
|
||||
```
|
||||
|
||||
### **9. Implement Comment Reply Features**
|
||||
**Location**: `sojorn_app/lib/widgets/threaded_comment_widget.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 2 hours
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement actual reply submission in `_submitReply()`
|
||||
- [ ] Add reply UI components
|
||||
- [ ] Connect to backend reply API
|
||||
- [ ] Remove "TODO: Implement actual reply submission" comment
|
||||
|
||||
### **10. Add Post Options Menu**
|
||||
**Location**: `sojorn_app/lib/widgets/post_with_video_widget.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement post options menu functionality
|
||||
- [ ] Add menu items (edit, delete, report, etc.)
|
||||
- [ ] Remove "TODO: Show post options" comment
|
||||
- [ ] Connect to backend APIs
|
||||
|
||||
### **11. Fix Profile Navigation**
|
||||
**Location**: `sojorn_app/lib/widgets/sojorn_rich_text.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement profile navigation from mentions
|
||||
- [ ] Remove "TODO: Implement profile navigation" comment
|
||||
- [ ] Add proper navigation logic
|
||||
- [ ] Test mention navigation
|
||||
|
||||
### **12. Clean Up Debug Code**
|
||||
**Location**: `sojorn_app/lib/services/simple_e2ee_service.dart`
|
||||
**Status**: Not Started
|
||||
**Effort**: 1 hour
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Remove `_FORCE_KEY_ROTATION` debug flag
|
||||
- [ ] Remove debug print statements
|
||||
- [ ] Remove force reset methods for 208-bit key bug
|
||||
- [ ] Clean up any remaining debug code
|
||||
**Backend**: `go-backend/internal/handlers/post_handler.go` (beacon endpoints)
|
||||
**Frontend**: `sojorn_app/lib/screens/beacons/`
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Already Completed Features** (For Reference)
|
||||
## ⚠️ Medium Priority — Core Features
|
||||
|
||||
### **Core Functionality**
|
||||
- ✅ User authentication (JWT-based)
|
||||
- ✅ Post creation, editing, deletion
|
||||
- ✅ Feed and post retrieval
|
||||
- ✅ Image/video uploads to R2
|
||||
- ✅ **Comment system** (fully implemented)
|
||||
### 4. User Profile Customization — Modular Widget System
|
||||
**Status**: Basic profiles exist. Needs a modular, personalized approach.
|
||||
Vision: New-age MySpace — users pick and arrange profile widgets to make it their own, without chaos.
|
||||
|
||||
**Core Architecture:**
|
||||
- Profile is a grid/stack of draggable **widgets** the user can add, remove, and reorder
|
||||
- Each widget is a self-contained component with a fixed max size and style boundary
|
||||
- Widgets render inside a consistent design system (can't break the layout or go full HTML)
|
||||
- Profile data stored as a JSON `profile_layout` column: ordered list of widget types + config
|
||||
|
||||
**Standard Fields (always present):**
|
||||
- [ ] Avatar + display name + handle (non-removable header)
|
||||
- [ ] Bio (rich text, links, emoji)
|
||||
- [ ] Pronouns field
|
||||
- [ ] Location (optional, city-level)
|
||||
|
||||
**Widget Catalog (user picks and arranges):**
|
||||
- [ ] **Pinned Posts** — Pin up to 3 posts to the top of your profile
|
||||
- [ ] **Music Widget** — Currently listening / favorite track (Spotify/Apple Music embed or manual)
|
||||
- [ ] **Photo Grid** — Mini gallery (3-6 featured photos from uploads)
|
||||
- [ ] **Social Links** — Icons row for external links (site, GitHub, IG, etc.)
|
||||
- [ ] **Causes I Care About** — Tag-style badges (environment, mutual aid, arts, etc.)
|
||||
- [ ] **Featured Friends** — Highlight 3-6 people (like MySpace Top 8 but chill)
|
||||
- [ ] **Stats Widget** — Post count, follower count, member since (opt-in)
|
||||
- [ ] **Quote Widget** — A single styled quote / motto
|
||||
- [ ] **Beacon Activity** — Recent community contributions
|
||||
- [ ] **Custom Text Block** — Markdown-rendered freeform section
|
||||
|
||||
**Theming (constrained but expressive):**
|
||||
- [ ] Accent color picker (applies to profile header, widget borders, link color)
|
||||
- [ ] Light/dark/auto profile theme (independent of app theme)
|
||||
- [ ] Banner image (behind header area)
|
||||
- [ ] Profile badges (verified, early adopter, community helper — system-assigned)
|
||||
|
||||
**Implementation:**
|
||||
- [ ] Backend: `profile_layout JSONB` column on `profiles` table
|
||||
- [ ] Backend: `PUT /profile/layout` endpoint to save widget arrangement
|
||||
- [ ] Frontend: `ProfileWidgetRenderer` that reads layout JSON and renders widget stack
|
||||
- [ ] Frontend: `ProfileEditor` with drag-to-reorder and add/remove widget catalog
|
||||
- [ ] Widget sandboxing — each widget has max height, no custom CSS/HTML injection
|
||||
- [ ] Default layout for new users (bio + social links + pinned posts)
|
||||
|
||||
---
|
||||
|
||||
### 5. Blocking System
|
||||
**Status**: Basic block exists. Import/export not implemented.
|
||||
|
||||
- [ ] Verify block prevents: seeing posts, DMs, mentions, search results, follow
|
||||
- [ ] Block list management screen (view, unblock)
|
||||
- [ ] Export block list as JSON/CSV
|
||||
- [ ] Import block list from JSON/CSV
|
||||
- [ ] Import block list from other platforms (Twitter/X format, Mastodon format)
|
||||
- [ ] Blocked users cannot see your profile or posts
|
||||
- [ ] Silent block (user doesn't know they're blocked)
|
||||
|
||||
**Frontend**: `sojorn_app/lib/screens/profile/blocked_users_screen.dart`
|
||||
**Backend**: `go-backend/internal/handlers/user_handler.go`
|
||||
|
||||
---
|
||||
|
||||
### 6. E2EE Chat Stability & Sync
|
||||
**Status**: X3DH implementation works but key sync across devices is fragile.
|
||||
|
||||
- [ ] Audit key recovery flow — ensure it reliably recovers from MAC errors
|
||||
- [ ] Device-to-device key sync without storing plaintext on server
|
||||
- [ ] QR code key verification between users
|
||||
- [ ] "Encrypted with old keys" messages should offer re-request option
|
||||
- [ ] Clean up `forceResetBrokenKeys()` dead code in `simple_e2ee_service.dart`
|
||||
- [ ] Ensure cloud backup/restore cycle works end-to-end
|
||||
- [ ] Add key fingerprint display in chat settings
|
||||
- [ ] Rate limit key recovery to prevent loops
|
||||
|
||||
---
|
||||
|
||||
### 7. Repost / Boost Feature
|
||||
**Status**: Not started.
|
||||
A "repost" action that amplifies content to your followers without quote-posting.
|
||||
|
||||
- [ ] Repost button on posts (share to your followers' feeds)
|
||||
- [ ] Repost count displayed on posts
|
||||
- [ ] Reposted-by attribution in feed ("@user reposted")
|
||||
- [ ] Undo repost
|
||||
- [ ] Backend: `reposts` table (user_id, post_id, created_at)
|
||||
- [ ] Feed algorithm weights reposts into feed ranking
|
||||
|
||||
---
|
||||
|
||||
### 8. Algorithm Refactor — Promote Good, Discourage Anger
|
||||
**Status**: Basic algorithm exists in `algorithm_config` table. Needs philosophical overhaul.
|
||||
Core principle: **Show users what they love, not what they hate.**
|
||||
|
||||
- [ ] Engagement scoring that weights positive interactions (save, repost, thoughtful reply) over rage-clicks
|
||||
- [ ] De-rank content with high negative-reaction ratios
|
||||
- [ ] "Cooling period" — delay viral anger content by 30min before amplifying
|
||||
- [ ] Boost content tagged as good news, community, mutual aid, creativity
|
||||
- [ ] User-controllable feed preferences ("show me more of X, less of Y")
|
||||
- [ ] Diversity injection — prevent echo chambers by mixing in adjacent-interest content
|
||||
- [ ] Transparency: show users why a post appeared in their feed
|
||||
- [ ] Admin algorithm tuning panel (already built in admin dashboard)
|
||||
- [ ] A/B testing framework for algorithm changes
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Low Priority — Polish & Cleanup
|
||||
|
||||
### 9. Remaining Code TODOs
|
||||
Small scattered items across the codebase:
|
||||
|
||||
- [ ] `sojorn_rich_text.dart` — Implement profile navigation from @mentions
|
||||
- [ ] `post_with_video_widget.dart` — Implement post options menu (edit, delete, report)
|
||||
- [ ] `video_player_with_comments.dart` — Implement "more options" button
|
||||
- [ ] `sojorn_swipeable_post.dart` — Wire up allowChain setting when API supports it
|
||||
- [ ] `reading_post_card.dart` — Implement share functionality
|
||||
|
||||
### 10. Legacy Cleanup
|
||||
- [ ] Delete `go-backend/cmd/supabase-migrate/` directory (dead migration tool)
|
||||
- [ ] Update 2 stale Supabase comments in `go-backend/internal/middleware/auth.go`
|
||||
- [ ] Remove `forceResetBrokenKeys()` from `simple_e2ee_service.dart`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Completed
|
||||
|
||||
### Core Platform (shipped)
|
||||
- ✅ Go backend — 100% migrated from Supabase
|
||||
- ✅ User auth (JWT, refresh tokens, email verification)
|
||||
- ✅ Posts (create, edit, delete, visibility, chains)
|
||||
- ✅ Comments (threaded, with replies)
|
||||
- ✅ Feed algorithm (basic version)
|
||||
- ✅ Image/video uploads to Cloudflare R2
|
||||
- ✅ Follow/unfollow system
|
||||
- ✅ E2EE chat (X3DH implementation)
|
||||
- ✅ Push notifications (FCM)
|
||||
- ✅ Search functionality
|
||||
- ✅ Search (users, posts, hashtags)
|
||||
- ✅ Categories and user settings
|
||||
- ✅ Chain posts functionality
|
||||
- ✅ Chain posts
|
||||
- ✅ Beacon voting (vouch, report, remove vote)
|
||||
- ✅ E2EE chat (X3DH, key backup/restore)
|
||||
- ✅ Push notifications (FCM)
|
||||
- ✅ Quips (basic video recording and feed)
|
||||
|
||||
### **Infrastructure**
|
||||
- ✅ Go backend API with all core endpoints
|
||||
- ✅ PostgreSQL database with proper schema
|
||||
- ✅ Cloudflare R2 integration for media
|
||||
- ✅ Nginx reverse proxy
|
||||
- ✅ SSL/TLS configuration
|
||||
### Admin & Moderation (shipped)
|
||||
- ✅ Admin panel (Next.js dashboard, users, posts, moderation queue, appeals)
|
||||
- ✅ OpenAI text moderation (auto-flag on post/comment creation)
|
||||
- ✅ Three Poisons scoring (Hate, Greed, Delusion)
|
||||
- ✅ Ban/suspend system with content jailing
|
||||
- ✅ Email notifications (ban, suspend, restore, content removal)
|
||||
- ✅ Moderation queue with dismiss/action/ban workflows
|
||||
- ✅ User violation tracking and history
|
||||
- ✅ IP-based ban evasion detection
|
||||
|
||||
### Infrastructure (shipped)
|
||||
- ✅ PostgreSQL with full schema
|
||||
- ✅ Cloudflare R2 media storage
|
||||
- ✅ Nginx reverse proxy + SSL/TLS
|
||||
- ✅ Systemd service management
|
||||
- ✅ GeoIP for location features
|
||||
- ✅ Automated deploy scripts
|
||||
|
||||
---
|
||||
|
||||
## 📊 **Current Status Analysis**
|
||||
|
||||
### **What's Working Better Than Expected**
|
||||
- **Comment System**: Fully implemented with Go backend (TODO was outdated)
|
||||
- **Media Uploads**: Direct to R2, no Supabase dependency
|
||||
- **Chain Posts**: Complete implementation
|
||||
- **E2EE Chat**: Production-ready X3DH system
|
||||
|
||||
### **What Actually Needs Work**
|
||||
- **Beacon Voting**: Only 3 missing endpoints (core feature)
|
||||
- **Supabase Cleanup**: Mostly removing legacy code
|
||||
- **UI Polish**: Fixing outdated TODO comments
|
||||
|
||||
### **Key Insight**
|
||||
The codebase is **90% complete** for Go backend migration. Most TODO comments are outdated and refer to features that are already implemented. The beacon voting system is the only critical missing piece.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Implementation Plan**
|
||||
|
||||
### **Day 1: Beacon Voting (4 hours)**
|
||||
1. Implement beacon voting handlers (2h)
|
||||
2. Add API routes (30m)
|
||||
3. Update Flutter API methods (1h)
|
||||
4. Test beacon voting flow (30m)
|
||||
|
||||
### **Day 2: Supabase Cleanup (3 hours)**
|
||||
1. Remove function proxy (1h)
|
||||
2. Clean up dependencies (2h)
|
||||
|
||||
### **Day 3: UI Polish (3 hours)**
|
||||
1. Fix video comments TODO (1h)
|
||||
2. Implement reply features (2h)
|
||||
|
||||
### **Day 4: Final Polish & Testing (2 hours)**
|
||||
1. Add post options menu (1h)
|
||||
2. End-to-end testing (1h)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Success Criteria**
|
||||
|
||||
### **100% Go Backend Functionality**
|
||||
- [ ] All features work without Supabase
|
||||
- [ ] Beacon voting system operational
|
||||
- [ ] No Supabase code or dependencies
|
||||
- [ ] All TODO comments resolved or updated
|
||||
- [ ] End-to-end testing passes
|
||||
|
||||
### **Code Quality**
|
||||
- [ ] No debug code in production
|
||||
- [ ] No outdated comments
|
||||
- [ ] Clean, maintainable code
|
||||
- [ ] Proper error handling
|
||||
- [ ] Security best practices
|
||||
|
||||
---
|
||||
|
||||
## 📝 **Notes**
|
||||
|
||||
- **Most TODO comments are outdated** - features are already implemented
|
||||
- **Beacon voting is the only critical missing feature**
|
||||
- **Supabase cleanup is mostly removing legacy code**
|
||||
- **UI polish items are nice-to-have, not blocking**
|
||||
|
||||
**Total estimated effort: ~12 hours to reach 100% Go backend functionality**
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Start with high-priority beacon voting implementation, as it's the only critical missing feature for complete Go backend functionality.
|
||||
### Recent Fixes (Feb 2026)
|
||||
- ✅ Notification badge count clears on archive
|
||||
- ✅ Notification UI → full page (was slide-up dialog)
|
||||
- ✅ Moderation queue constraint fix (dismissed/actioned statuses)
|
||||
- ✅ Debug print cleanup (190+ statements removed)
|
||||
- ✅ Run scripts cleanup (run_dev, run_web, run_web_chrome)
|
||||
|
|
|
|||
Loading…
Reference in a new issue