@@ -9,9 +9,8 @@ use sea_orm::{
99 ColumnTrait , DatabaseConnection , EntityTrait , PaginatorTrait , QueryFilter , QueryOrder ,
1010 QuerySelect ,
1111} ;
12- use std:: collections:: HashMap ;
1312use std:: sync:: Arc ;
14- use temps_entities:: { audit_logs , deployments, events, projects, users } ;
13+ use temps_entities:: { deployments, events, projects} ;
1514use tracing:: { error, info} ;
1615
1716pub struct DigestService {
@@ -92,18 +91,11 @@ impl DigestService {
9291 digest. funnels = self . aggregate_funnel_data ( week_start, week_end) . await . ok ( ) ;
9392 }
9493
95- if sections. security {
96- digest. security = self
97- . aggregate_security_data ( week_start, week_end)
94+ if sections. projects {
95+ digest. projects = self
96+ . aggregate_project_data ( week_start, week_end)
9897 . await
99- . ok ( ) ;
100- }
101-
102- if sections. resources {
103- digest. resources = self
104- . aggregate_resource_data ( week_start, week_end)
105- . await
106- . ok ( ) ;
98+ . unwrap_or_default ( ) ;
10799 }
108100
109101 // Build executive summary
@@ -257,55 +249,87 @@ impl DigestService {
257249 } )
258250 }
259251
260- /// Aggregate security and access data
261- async fn aggregate_security_data (
252+ /// Aggregate individual project statistics
253+ async fn aggregate_project_data (
262254 & self ,
263255 week_start : DateTime < Utc > ,
264256 week_end : DateTime < Utc > ,
265- ) -> Result < SecurityData > {
266- // Count new user signups
267- let new_user_signups = users:: Entity :: find ( )
268- . filter ( users:: Column :: CreatedAt . between ( week_start, week_end) )
269- . count ( self . db . as_ref ( ) )
270- . await ? as i64 ;
271-
272- // Aggregate audit logs by operation type
273- let audit_logs_list = audit_logs:: Entity :: find ( )
274- . filter ( audit_logs:: Column :: CreatedAt . between ( week_start, week_end) )
275- . all ( self . db . as_ref ( ) )
276- . await ?;
257+ ) -> Result < Vec < ProjectStats > > {
258+ use sea_orm:: { ColumnTrait , EntityTrait , QueryFilter , QuerySelect } ;
259+ use temps_entities:: { deployments, events, projects} ;
260+
261+ // Get all projects
262+ let all_projects = projects:: Entity :: find ( ) . all ( self . db . as_ref ( ) ) . await ?;
263+
264+ let mut project_stats = Vec :: new ( ) ;
265+
266+ for project in all_projects {
267+ // Count unique sessions for this project
268+ let visitors = events:: Entity :: find ( )
269+ . filter ( events:: Column :: ProjectId . eq ( project. id ) )
270+ . filter ( events:: Column :: Timestamp . between ( week_start, week_end) )
271+ . filter ( events:: Column :: SessionId . is_not_null ( ) )
272+ . select_only ( )
273+ . column ( events:: Column :: SessionId )
274+ . distinct ( )
275+ . count ( self . db . as_ref ( ) )
276+ . await ? as i64 ;
277+
278+ // Count page views for this project
279+ let page_views = events:: Entity :: find ( )
280+ . filter ( events:: Column :: ProjectId . eq ( project. id ) )
281+ . filter ( events:: Column :: Timestamp . between ( week_start, week_end) )
282+ . count ( self . db . as_ref ( ) )
283+ . await ? as i64 ;
284+
285+ // Count deployments for this project
286+ let deployment_count = deployments:: Entity :: find ( )
287+ . filter ( deployments:: Column :: ProjectId . eq ( project. id ) )
288+ . filter ( deployments:: Column :: CreatedAt . between ( week_start, week_end) )
289+ . count ( self . db . as_ref ( ) )
290+ . await ? as i64 ;
291+
292+ // Calculate previous week visitors for trend
293+ let prev_week_start = week_start - Duration :: days ( 7 ) ;
294+ let prev_week_end = week_start;
295+
296+ let prev_visitors = events:: Entity :: find ( )
297+ . filter ( events:: Column :: ProjectId . eq ( project. id ) )
298+ . filter ( events:: Column :: Timestamp . between ( prev_week_start, prev_week_end) )
299+ . filter ( events:: Column :: SessionId . is_not_null ( ) )
300+ . select_only ( )
301+ . column ( events:: Column :: SessionId )
302+ . distinct ( )
303+ . count ( self . db . as_ref ( ) )
304+ . await ? as i64 ;
305+
306+ let week_over_week_change = if prev_visitors > 0 {
307+ ( ( visitors - prev_visitors) as f64 / prev_visitors as f64 ) * 100.0
308+ } else if visitors > 0 {
309+ 100.0 // If we had 0 before and now have some, that's 100% increase
310+ } else {
311+ 0.0
312+ } ;
277313
278- let mut audit_log_summary: HashMap < String , i64 > = HashMap :: new ( ) ;
279- for log in audit_logs_list {
280- * audit_log_summary. entry ( log. operation_type ) . or_insert ( 0 ) += 1 ;
314+ // Only include projects that have activity
315+ if visitors > 0 || page_views > 0 || deployment_count > 0 {
316+ project_stats. push ( ProjectStats {
317+ project_id : project. id ,
318+ project_name : project. name . clone ( ) ,
319+ project_slug : project. slug . clone ( ) ,
320+ visitors,
321+ page_views,
322+ unique_sessions : visitors, // Same as visitors (unique sessions)
323+ deployments : deployment_count,
324+ week_over_week_change,
325+ } ) ;
326+ }
281327 }
282328
283- Ok ( SecurityData {
284- new_user_signups,
285- git_provider_connections : 0 ,
286- api_key_usage : 0 ,
287- suspicious_activities : vec ! [ ] ,
288- audit_log_summary,
289- } )
290- }
329+ // Sort projects by visitors (most active first)
330+ project_stats. sort_by ( |a, b| b. visitors . cmp ( & a. visitors ) ) ;
291331
292- /// Aggregate resource and cost data
293- async fn aggregate_resource_data (
294- & self ,
295- __week_start : DateTime < Utc > ,
296- __week_end : DateTime < Utc > ,
297- ) -> Result < ResourceData > {
298- // TODO: Implement resource usage queries (S3, database size, etc.)
299- Ok ( ResourceData {
300- storage_used_mb : 0.0 ,
301- storage_growth_mb : 0.0 ,
302- database_size_mb : 0.0 ,
303- database_growth_mb : 0.0 ,
304- log_volume_mb : 0.0 ,
305- backup_count : 0 ,
306- backup_size_mb : 0.0 ,
307- recommendations : vec ! [ ] ,
308- } )
332+ Ok ( project_stats)
309333 }
310334
311335 /// Build executive summary from aggregated data
@@ -482,19 +506,18 @@ mod tests {
482506 }
483507
484508 #[ tokio:: test]
485- async fn test_aggregate_security_data_empty ( ) {
509+ async fn test_aggregate_project_data_empty ( ) {
486510 let ( service, test_db) = setup_test_service ( ) . await ;
487511
488512 let now = Utc :: now ( ) ;
489513 let week_start = now - Duration :: days ( 7 ) ;
490514
491- let security = service
492- . aggregate_security_data ( week_start, now)
515+ let projects = service
516+ . aggregate_project_data ( week_start, now)
493517 . await
494- . expect ( "Failed to aggregate security data" ) ;
518+ . expect ( "Failed to aggregate project data" ) ;
495519
496- assert_eq ! ( security. new_user_signups, 0 ) ;
497- assert ! ( security. audit_log_summary. is_empty( ) ) ;
520+ assert_eq ! ( projects. len( ) , 0 ) ;
498521
499522 test_db. cleanup_all_tables ( ) . await . expect ( "Cleanup failed" ) ;
500523 }
@@ -758,31 +781,82 @@ mod tests {
758781 }
759782
760783 #[ tokio:: test]
761- async fn test_aggregate_security_with_real_users ( ) {
784+ async fn test_aggregate_project_data_with_activity ( ) {
762785 let ( service, test_db) = setup_test_service ( ) . await ;
763786
764787 let now = Utc :: now ( ) ;
765788 let week_start = now - Duration :: days ( 7 ) ;
766789
767- // Create test users in current week
768- for i in 0 ..3 {
769- let user = users:: ActiveModel {
770- name : Set ( format ! ( "User {}" , i) ) ,
771- email : Set ( format ! ( "user{}@example.com" , i) ) ,
772- password_hash : Set ( Some ( "hash" . to_string ( ) ) ) ,
773- created_at : Set ( now - Duration :: hours ( i as i64 ) ) ,
774- updated_at : Set ( now) ,
790+ // Create test project
791+ let project = projects:: ActiveModel {
792+ name : Set ( "test-project" . to_string ( ) ) ,
793+ slug : Set ( "test-project" . to_string ( ) ) ,
794+ repo_name : Set ( "test-repo" . to_string ( ) ) ,
795+ repo_owner : Set ( "test-owner" . to_string ( ) ) ,
796+ directory : Set ( "/" . to_string ( ) ) ,
797+ main_branch : Set ( "main" . to_string ( ) ) ,
798+ preset : Set ( temps_entities:: preset:: Preset :: Astro ) ,
799+ created_at : Set ( now) ,
800+ updated_at : Set ( now) ,
801+ ..Default :: default ( )
802+ } ;
803+ let project = project. insert ( test_db. connection ( ) ) . await . unwrap ( ) ;
804+
805+ // Create test environment
806+ let environment = environments:: ActiveModel {
807+ project_id : Set ( project. id ) ,
808+ name : Set ( "production" . to_string ( ) ) ,
809+ slug : Set ( "production" . to_string ( ) ) ,
810+ subdomain : Set ( "production" . to_string ( ) ) ,
811+ host : Set ( "production.example.com" . to_string ( ) ) ,
812+ upstreams : Set ( temps_entities:: upstream_config:: UpstreamList :: default ( ) ) ,
813+ created_at : Set ( now) ,
814+ updated_at : Set ( now) ,
815+ ..Default :: default ( )
816+ } ;
817+ let environment = environment. insert ( test_db. connection ( ) ) . await . unwrap ( ) ;
818+
819+ // Create test deployment
820+ let deployment = deployments:: ActiveModel {
821+ project_id : Set ( project. id ) ,
822+ environment_id : Set ( environment. id ) ,
823+ slug : Set ( "deploy-1" . to_string ( ) ) ,
824+ state : Set ( "completed" . to_string ( ) ) ,
825+ metadata : Set ( Some ( deployments:: DeploymentMetadata :: default ( ) ) ) ,
826+ commit_sha : Set ( Some ( "abc123" . to_string ( ) ) ) ,
827+ branch_ref : Set ( Some ( "refs/heads/main" . to_string ( ) ) ) ,
828+ created_at : Set ( now) ,
829+ updated_at : Set ( now) ,
830+ ..Default :: default ( )
831+ } ;
832+ let deployment = deployment. insert ( test_db. connection ( ) ) . await . unwrap ( ) ;
833+
834+ // Create test events (simulating visitors and page views)
835+ for i in 0 ..5 {
836+ let event = events:: ActiveModel {
837+ project_id : Set ( project. id ) ,
838+ environment_id : Set ( Some ( environment. id ) ) ,
839+ deployment_id : Set ( Some ( deployment. id ) ) ,
840+ session_id : Set ( Some ( format ! ( "session-{}" , i) ) ) ,
841+ event_type : Set ( "pageview" . to_string ( ) ) ,
842+ timestamp : Set ( now - Duration :: hours ( i as i64 ) ) ,
843+ hostname : Set ( "example.com" . to_string ( ) ) ,
844+ pathname : Set ( "/" . to_string ( ) ) ,
845+ page_path : Set ( "/" . to_string ( ) ) ,
846+ href : Set ( "https://example.com/" . to_string ( ) ) ,
775847 ..Default :: default ( )
776848 } ;
777- user . insert ( test_db. connection ( ) ) . await . unwrap ( ) ;
849+ event . insert ( test_db. connection ( ) ) . await . unwrap ( ) ;
778850 }
779851
780- let security = service
781- . aggregate_security_data ( week_start, now)
852+ let projects_data = service
853+ . aggregate_project_data ( week_start, now)
782854 . await
783- . expect ( "Failed to aggregate security data" ) ;
855+ . expect ( "Failed to aggregate project data" ) ;
784856
785- assert_eq ! ( security. new_user_signups, 3 ) ;
857+ assert_eq ! ( projects_data. len( ) , 1 ) ;
858+ assert_eq ! ( projects_data[ 0 ] . project_name, "test-project" ) ;
859+ assert ! ( projects_data[ 0 ] . visitors > 0 ) ;
786860
787861 test_db. cleanup_all_tables ( ) . await . expect ( "Cleanup failed" ) ;
788862 }
@@ -899,7 +973,7 @@ mod tests {
899973 assert ! ( digest. has_data( ) ) ;
900974 assert ! ( digest. performance. is_some( ) ) ;
901975 assert ! ( digest. deployments. is_some( ) ) ;
902- assert ! ( digest. security . is_some ( ) ) ;
976+ assert ! ( ! digest. projects . is_empty ( ) ) ;
903977
904978 // Verify performance data
905979 let perf = digest. performance . unwrap ( ) ;
@@ -911,9 +985,9 @@ mod tests {
911985 assert_eq ! ( deploy. successful_deployments, 5 ) ; // 1 initial + 4 from loop
912986 assert_eq ! ( deploy. failed_deployments, 1 ) ;
913987
914- // Verify security data
915- let security = digest. security . unwrap ( ) ;
916- assert_eq ! ( security . new_user_signups , 2 ) ;
988+ // Verify project data
989+ assert_eq ! ( digest. projects . len ( ) , 1 ) ;
990+ assert_eq ! ( digest . projects [ 0 ] . project_name , "integration-test-project" ) ;
917991
918992 // Verify executive summary
919993 assert_eq ! ( digest. executive_summary. total_visitors, 10 ) ;
0 commit comments