Skip to main content

cloudillo_action/native_hooks/
fshr.rs

1//! FSHR (File Share) action native hooks
2//!
3//! Handles file sharing lifecycle:
4//! - on_receive: Sets status to 'C' (confirmation required) for incoming shares
5//! - on_accept: Creates file entry when user accepts the share
6
7use crate::hooks::{HookContext, HookResult};
8use crate::prelude::*;
9use cloudillo_core::app::App;
10use cloudillo_types::meta_adapter::{CreateFile, FileStatus, UpdateActionDataOptions};
11use cloudillo_types::types::Patch;
12
13/// FSHR on_receive hook - Handle incoming file share request
14///
15/// Logic:
16/// - If we are the audience and subType is not DEL, set status to 'C' (confirmation required)
17/// - DEL subtype doesn't require confirmation
18pub async fn on_receive(app: App, context: HookContext) -> ClResult<HookResult> {
19	let tn_id = TnId(context.tenant_id as u32);
20
21	tracing::debug!(
22		"Native hook: FSHR on_receive for action {} from {} to {:?}",
23		context.action_id,
24		context.issuer,
25		context.audience
26	);
27
28	// Check if we are the audience
29	let is_audience = context.audience.as_ref() == Some(&context.tenant_tag);
30
31	// Only require confirmation for non-DEL subtypes when we are the audience
32	if is_audience && context.subtype.as_deref() != Some("DEL") {
33		tracing::info!(
34			"FSHR: Received file share from {} - setting status to confirmation required",
35			context.issuer
36		);
37
38		let update_opts =
39			UpdateActionDataOptions { status: Patch::Value('C'), ..Default::default() };
40
41		if let Err(e) = app
42			.meta_adapter
43			.update_action_data(tn_id, &context.action_id, &update_opts)
44			.await
45		{
46			tracing::warn!("FSHR: Failed to update action status to 'C': {}", e);
47		}
48	}
49
50	Ok(HookResult::default())
51}
52
53/// FSHR on_accept hook - Create file entry when user accepts the share
54///
55/// Logic:
56/// - Parse content to get fileName and contentType
57/// - Create file entry with status 'M' (mutable/shared) and owner_tag from issuer
58pub async fn on_accept(app: App, context: HookContext) -> ClResult<HookResult> {
59	let tn_id = TnId(context.tenant_id as u32);
60
61	tracing::debug!(
62		"Native hook: FSHR on_accept for action {} from {}",
63		context.action_id,
64		context.issuer
65	);
66
67	// Parse content
68	let content = match &context.content {
69		Some(c) => c,
70		None => {
71			tracing::warn!("FSHR on_accept: Missing content");
72			return Ok(HookResult::default());
73		}
74	};
75
76	let content_type = match content.get("contentType").and_then(|v| v.as_str()) {
77		Some(ct) => ct,
78		None => {
79			tracing::warn!("FSHR on_accept: Missing contentType in content");
80			return Ok(HookResult::default());
81		}
82	};
83
84	let file_name = match content.get("fileName").and_then(|v| v.as_str()) {
85		Some(fn_) => fn_,
86		None => {
87			tracing::warn!("FSHR on_accept: Missing fileName in content");
88			return Ok(HookResult::default());
89		}
90	};
91
92	let file_tp = match content.get("fileTp").and_then(|v| v.as_str()) {
93		Some(ft) => ft,
94		None => {
95			tracing::warn!("FSHR on_accept: Missing fileTp in content");
96			return Ok(HookResult::default());
97		}
98	};
99
100	// Subject contains the file_id
101	let file_id = match &context.subject {
102		Some(s) => s,
103		None => {
104			tracing::warn!("FSHR on_accept: Missing subject (file_id)");
105			return Ok(HookResult::default());
106		}
107	};
108
109	tracing::info!(
110		"FSHR: Accepting file share - creating file entry for {} from {} (type: {})",
111		file_id,
112		context.issuer,
113		file_tp
114	);
115
116	// Create file entry with status 'A' (active) and visibility direct (most restricted - owner and tenant can see)
117	let create_opts = CreateFile {
118		orig_variant_id: None,
119		file_id: Some(file_id.clone().into()),
120		parent_id: None,
121		owner_tag: Some(context.issuer.clone().into()), // Shared files: owner is the sharer
122		creator_tag: None,
123		preset: None,
124		content_type: content_type.into(),
125		file_name: file_name.into(),
126		file_tp: Some(file_tp.into()),
127		created_at: None,
128		tags: None,
129		x: None,
130		visibility: None, // Direct - owner and tenant can see
131		status: Some(FileStatus::Active),
132	};
133
134	match app.meta_adapter.create_file(tn_id, create_opts).await {
135		Ok(file_result) => {
136			tracing::info!("FSHR: Created shared file entry: {:?}", file_result);
137		}
138		Err(e) => {
139			tracing::error!("FSHR: Failed to create file entry: {}", e);
140			return Err(e);
141		}
142	}
143
144	Ok(HookResult::default())
145}
146
147// vim: ts=4