@@ -57,6 +57,7 @@ import dowstodon from "../public/apps/dowstodon.png"
5757import fread from "../public/apps/fread.png"
5858import plfe from "../public/apps/pl-fe.png"
5959
60+ import { z } from "zod"
6061import type { StaticImageData } from "next/legacy/image"
6162
6263export type appsList = {
@@ -82,6 +83,53 @@ export type appsList = {
8283 source_url ?: string
8384 } [ ]
8485}
86+
87+ // Zod schema for runtime validation
88+ const appSchema = z
89+ . object ( {
90+ name : z . string ( ) . min ( 1 , "App name is required" ) ,
91+ icon : z . any ( ) ,
92+ url : z . string ( ) . url ( "App URL must be a valid URL" ) ,
93+ released_on : z . string ( ) . optional ( ) ,
94+ paid : z . boolean ( ) . optional ( ) ,
95+ hidden_from_all : z . boolean ( ) . optional ( ) ,
96+ categoryLabel : z . string ( ) . optional ( ) ,
97+ open : z . boolean ( ) . optional ( ) ,
98+ source_url : z . string ( ) . url ( "Source URL must be a valid URL" ) . optional ( ) ,
99+ } )
100+ . refine (
101+ ( data ) => {
102+ // If open source, source_url must be provided
103+ if ( data . open === true && ! data . source_url ) {
104+ return false
105+ }
106+ return true
107+ } ,
108+ {
109+ message : "Open source apps (open: true) must have a source_url" ,
110+ }
111+ )
112+
113+ const appsListSchema = z . record ( z . string ( ) , z . array ( appSchema ) )
114+
115+ /**
116+ * Validates the apps data structure at runtime.
117+ * Throws an error if validation fails.
118+ */
119+ function validateApps ( appsData : appsList ) : void {
120+ try {
121+ appsListSchema . parse ( appsData )
122+ } catch ( error ) {
123+ if ( error instanceof z . ZodError ) {
124+ const errors = error . errors . map (
125+ ( err ) =>
126+ `${ err . path . join ( "." ) } : ${ err . message } ${ err . code === "custom" ? `(${ JSON . stringify ( err . params ) } )` : "" } `
127+ )
128+ throw new Error ( `Apps validation failed:\n${ errors . join ( "\n" ) } ` )
129+ }
130+ throw error
131+ }
132+ }
85133export const apps : appsList = {
86134 android : [
87135 {
@@ -123,6 +171,8 @@ export const apps: appsList = {
123171 icon : fedilab ,
124172 url : "https://play.google.com/store/apps/details?id=app.fedilab.android" ,
125173 paid : false ,
174+ open : true ,
175+ source_url : "https://codeberg.org/tom79/Fedilab" ,
126176 } ,
127177 {
128178 released_on : "Jan 26, 2023" ,
@@ -193,6 +243,8 @@ export const apps: appsList = {
193243 name : "iMast" ,
194244 icon : imast ,
195245 url : "https://apps.apple.com/app/imast/id1229461703" ,
246+ open : true ,
247+ source_url : "https://github.com/cinderella-project/iMast" ,
196248 } ,
197249 {
198250 released_on : "Jan 19, 2023" ,
@@ -221,7 +273,7 @@ export const apps: appsList = {
221273 released_on : "Mar 24, 2023" ,
222274 name : "Woolly" ,
223275 icon : woolly ,
224- url : "https://apps.apple.com/us/ app/woolly-for-mastodon/id6444360628" ,
276+ url : "https://apps.apple.com/app/woolly-for-mastodon/id6444360628" ,
225277 paid : true ,
226278 open : false ,
227279 } ,
@@ -286,7 +338,7 @@ export const apps: appsList = {
286338 released_on : "Aug 10, 2023" ,
287339 name : "SoraSNS" ,
288340 icon : sora ,
289- url : "https://apps.apple.com/us/ app/sorasns-for-mastodon-bluesky/id6754866904" ,
341+ url : "https://apps.apple.com/app/sorasns-for-mastodon-bluesky/id6754866904" ,
290342 paid : false ,
291343 open : false ,
292344 } ,
@@ -302,12 +354,12 @@ export const apps: appsList = {
302354 released_on : "Jun 3, 2024" ,
303355 name : "Oxpecker (watchOS)" ,
304356 icon : oxpecker ,
305- url : "https://apps.apple.com/app/oxpecker-for-mastodon/id6474893905?platform=appleWatch " ,
357+ url : "https://apps.apple.com/app/oxpecker-for-mastodon/id6474893905" ,
306358 paid : true ,
307359 open : false ,
308360 } ,
309361 {
310- released_on : "July 10th , 2024" ,
362+ released_on : "Jul 10 , 2024" ,
311363 name : "Bubble" ,
312364 icon : bubble ,
313365 url : "https://apps.apple.com/app/bubble/id6477757490" ,
@@ -319,7 +371,7 @@ export const apps: appsList = {
319371 released_on : "Jan 1, 2024" ,
320372 name : "Odous (watchOS)" ,
321373 icon : odous ,
322- url : "https://apps.apple.com/us/ app/id6446084064" ,
374+ url : "https://apps.apple.com/app/id6446084064" ,
323375 paid : true ,
324376 open : false ,
325377 } ,
@@ -372,7 +424,7 @@ export const apps: appsList = {
372424 icon : tooty ,
373425 url : "https://n1k0.github.io/tooty/v2/" ,
374426 open : true ,
375- source_url : "https://github.com/n1k0/tooty " ,
427+ source_url : "https://github.com/n1k0/tooty" ,
376428 } ,
377429 {
378430 name : "Mastodeck" ,
@@ -509,34 +561,39 @@ export const apps: appsList = {
509561 icon : amidon ,
510562 url : "https://github.com/BlitterStudio/amidon" ,
511563 open : true ,
564+ source_url : "https://github.com/BlitterStudio/amidon" ,
512565 } ,
513566 {
514567 released_on : "Feb 5, 2023" ,
515568 name : "BREXXTODON" ,
516569 icon : brexxtodon ,
517570 url : "https://github.com/mainframed/BREXXTODON" ,
518571 open : true ,
572+ source_url : "https://github.com/mainframed/BREXXTODON" ,
519573 } ,
520574 {
521575 released_on : "Nov 14, 2022" ,
522576 name : "DOStodon" ,
523577 icon : dostodon ,
524578 url : "https://github.com/SuperIlu/DOStodon" ,
525579 open : true ,
580+ source_url : "https://github.com/SuperIlu/DOStodon" ,
526581 } ,
527582 {
528583 released_on : "Nov 20, 2022" ,
529584 name : "Macstodon" ,
530585 icon : macstodon ,
531586 url : "https://github.com/smallsco/macstodon" ,
532587 open : true ,
588+ source_url : "https://github.com/smallsco/macstodon" ,
533589 } ,
534590 {
535591 released_on : "Apr 14, 2023" ,
536592 name : "Masto9" ,
537593 icon : mastonine ,
538594 url : "https://sr.ht/~julienxx/Masto9/" ,
539595 open : true ,
596+ source_url : "https://sr.ht/~julienxx/Masto9/" ,
540597 } ,
541598 {
542599 released_on : "Mar 6, 2023" ,
@@ -552,13 +609,15 @@ export const apps: appsList = {
552609 icon : mastodonforworkgroups ,
553610 url : "https://github.com/meyskens/mastodon-for-workgroups" ,
554611 open : true ,
612+ source_url : "https://github.com/meyskens/mastodon-for-workgroups" ,
555613 } ,
556614 {
557615 released_on : "Sep 12, 2023" ,
558616 name : "Heffalump" ,
559617 icon : heffalump ,
560618 url : "https://github.com/knickish/heffalump" ,
561619 open : true ,
620+ source_url : "https://github.com/knickish/heffalump" ,
562621 } ,
563622 {
564623 released_on : "Sep 17, 2023" ,
@@ -567,6 +626,7 @@ export const apps: appsList = {
567626 url : "https://github.com/Havoc6502/MOStodon" ,
568627 paid : false ,
569628 open : true ,
629+ source_url : "https://github.com/Havoc6502/MOStodon" ,
570630 } ,
571631 {
572632 released_on : "Jan 06, 2018" ,
@@ -579,3 +639,6 @@ export const apps: appsList = {
579639 } ,
580640 ] ,
581641}
642+
643+ // Validate apps data at module load time
644+ validateApps ( apps )
0 commit comments