@@ -71,52 +71,14 @@ pub async fn get_manifest(
7171 let icons = if let Some ( ref icon_url) = hackathon. app_icon_url {
7272 // When a custom icon is uploaded, use it for all sizes
7373 // The browser will scale it appropriately
74- vec ! [
75- ManifestIcon {
76- src: icon_url. clone( ) ,
77- sizes: "512x512" . to_string( ) ,
78- icon_type: guess_icon_type( icon_url) ,
79- } ,
80- ManifestIcon {
81- src: icon_url. clone( ) ,
82- sizes: "192x192" . to_string( ) ,
83- icon_type: guess_icon_type( icon_url) ,
84- } ,
85- ManifestIcon {
86- src: icon_url. clone( ) ,
87- sizes: "144x144" . to_string( ) ,
88- icon_type: guess_icon_type( icon_url) ,
89- } ,
90- ManifestIcon {
91- src: icon_url. clone( ) ,
92- sizes: "96x96" . to_string( ) ,
93- icon_type: guess_icon_type( icon_url) ,
94- } ,
95- ]
74+ vec ! [ ManifestIcon {
75+ src: icon_url. clone( ) ,
76+ sizes: "512x512" . to_string( ) ,
77+ icon_type: guess_icon_type( icon_url) ,
78+ } ]
9679 } else {
97- // Fall back to default icons
98- vec ! [
99- ManifestIcon {
100- src: "/th26_icons/android/android-launchericon-512-512.png" . to_string( ) ,
101- sizes: "512x512" . to_string( ) ,
102- icon_type: "image/png" . to_string( ) ,
103- } ,
104- ManifestIcon {
105- src: "/th26_icons/android/android-launchericon-192-192.png" . to_string( ) ,
106- sizes: "192x192" . to_string( ) ,
107- icon_type: "image/png" . to_string( ) ,
108- } ,
109- ManifestIcon {
110- src: "/th26_icons/android/android-launchericon-144-144.png" . to_string( ) ,
111- sizes: "144x144" . to_string( ) ,
112- icon_type: "image/png" . to_string( ) ,
113- } ,
114- ManifestIcon {
115- src: "/th26_icons/android/android-launchericon-96-96.png" . to_string( ) ,
116- sizes: "96x96" . to_string( ) ,
117- icon_type: "image/png" . to_string( ) ,
118- } ,
119- ]
80+ // Fall back to default icons (do not exist yet)
81+ vec ! [ ]
12082 } ;
12183
12284 // Build manifest with hackathon-specific data
@@ -159,3 +121,130 @@ fn guess_icon_type(url: &str) -> String {
159121 "image/png" . to_string ( )
160122 }
161123}
124+
125+ /// Serve manifest.json at root, extracting hackathon slug from Referer header
126+ pub async fn get_root_manifest (
127+ State ( state) : State < AppState > ,
128+ headers : HeaderMap ,
129+ ) -> impl IntoResponse {
130+ // Try to extract hackathon slug from Referer header
131+ let slug = headers
132+ . get ( "referer" )
133+ . and_then ( |r| r. to_str ( ) . ok ( ) )
134+ . and_then ( |referer| {
135+ // Parse URL like "https://example.com/h/th26/dashboard"
136+ // Extract the slug after "/h/"
137+ if let Some ( start) = referer. find ( "/h/" ) {
138+ let rest = & referer[ start + 3 ..] ;
139+ // Take until next "/" or end
140+ let slug = rest. split ( '/' ) . next ( ) ?;
141+ if !slug. is_empty ( ) {
142+ return Some ( slug. to_string ( ) ) ;
143+ }
144+ }
145+ None
146+ } ) ;
147+
148+ let Some ( slug) = slug else {
149+ // No hackathon context, return a default/generic manifest
150+ let manifest = WebManifest {
151+ name : "Terrier" . to_string ( ) ,
152+ short_name : "Terrier" . to_string ( ) ,
153+ description : "Hackathon management platform" . to_string ( ) ,
154+ start_url : "/" . to_string ( ) ,
155+ scope : "/" . to_string ( ) ,
156+ display : "fullscreen" . to_string ( ) ,
157+ background_color : "#F4F2F3" . to_string ( ) ,
158+ theme_color : "#F4F2F3" . to_string ( ) ,
159+ orientation : "portrait" . to_string ( ) ,
160+ icons : vec ! [ ] ,
161+ } ;
162+
163+ let mut headers = HeaderMap :: new ( ) ;
164+ headers. insert (
165+ "Content-Type" ,
166+ HeaderValue :: from_static ( "application/manifest+json" ) ,
167+ ) ;
168+ return ( headers, Json ( manifest) ) . into_response ( ) ;
169+ } ;
170+
171+ // Find the hackathon
172+ let hackathon = match hackathons:: Entity :: find ( )
173+ . filter ( hackathons:: Column :: Slug . eq ( & slug) )
174+ . one ( & state. db )
175+ . await
176+ {
177+ Ok ( Some ( h) ) => h,
178+ Ok ( None ) => {
179+ // Fallback to generic manifest
180+ let manifest = WebManifest {
181+ name : "Terrier" . to_string ( ) ,
182+ short_name : "Terrier" . to_string ( ) ,
183+ description : "Hackathon management platform" . to_string ( ) ,
184+ start_url : "/" . to_string ( ) ,
185+ scope : "/" . to_string ( ) ,
186+ display : "fullscreen" . to_string ( ) ,
187+ background_color : "#F4F2F3" . to_string ( ) ,
188+ theme_color : "#F4F2F3" . to_string ( ) ,
189+ orientation : "portrait" . to_string ( ) ,
190+ icons : vec ! [ ] ,
191+ } ;
192+
193+ let mut headers = HeaderMap :: new ( ) ;
194+ headers. insert (
195+ "Content-Type" ,
196+ HeaderValue :: from_static ( "application/manifest+json" ) ,
197+ ) ;
198+ return ( headers, Json ( manifest) ) . into_response ( ) ;
199+ }
200+ Err ( e) => {
201+ tracing:: error!( "Failed to fetch hackathon: {:?}" , e) ;
202+ return ( StatusCode :: INTERNAL_SERVER_ERROR , "Database error" ) . into_response ( ) ;
203+ }
204+ } ;
205+
206+ let scope = format ! ( "/h/{}/" , slug) ;
207+ let start_url = format ! ( "/h/{}/" , slug) ;
208+
209+ let theme_color = hackathon
210+ . theme_color
211+ . clone ( )
212+ . unwrap_or_else ( || "#F4F2F3" . to_string ( ) ) ;
213+ let background_color = hackathon
214+ . background_color
215+ . clone ( )
216+ . unwrap_or_else ( || "#F4F2F3" . to_string ( ) ) ;
217+
218+ let icons = if let Some ( ref icon_url) = hackathon. app_icon_url {
219+ vec ! [ ManifestIcon {
220+ src: icon_url. clone( ) ,
221+ sizes: "512x512" . to_string( ) ,
222+ icon_type: guess_icon_type( icon_url) ,
223+ } ]
224+ } else {
225+ vec ! [ ]
226+ } ;
227+
228+ let manifest = WebManifest {
229+ name : hackathon. name . clone ( ) ,
230+ short_name : hackathon. name . clone ( ) ,
231+ description : hackathon
232+ . description
233+ . unwrap_or_else ( || format ! ( "The app for {}!" , hackathon. name) ) ,
234+ start_url,
235+ scope,
236+ display : "fullscreen" . to_string ( ) ,
237+ background_color,
238+ theme_color,
239+ orientation : "portrait" . to_string ( ) ,
240+ icons,
241+ } ;
242+
243+ let mut headers = HeaderMap :: new ( ) ;
244+ headers. insert (
245+ "Content-Type" ,
246+ HeaderValue :: from_static ( "application/manifest+json" ) ,
247+ ) ;
248+
249+ ( headers, Json ( manifest) ) . into_response ( )
250+ }
0 commit comments