@@ -48,6 +48,33 @@ console.log(" Data dir:", path.join(PROJECT_ROOT, "data"));
4848const dataDir = path . join ( PROJECT_ROOT , "data" ) ;
4949await fs . ensureDir ( path . join ( dataDir , "repos" ) ) ;
5050
51+ // Ścieżka do pliku z ulubionymi
52+ const favouritesPath = path . join ( dataDir , "favourites.json" ) ;
53+
54+ // Inicjalizacja pliku z ulubionymi jeśli nie istnieje
55+ if ( ! ( await fs . pathExists ( favouritesPath ) ) ) {
56+ await fs . writeJson ( favouritesPath , { favourites : [ ] } ) ;
57+ }
58+
59+ // Funkcje do zarządzania ulubionymi
60+ async function loadFavourites ( ) {
61+ try {
62+ const data = await fs . readJson ( favouritesPath ) ;
63+ return data . favourites || [ ] ;
64+ } catch ( error ) {
65+ console . error ( "Błąd ładowania ulubionych:" , error ) ;
66+ return [ ] ;
67+ }
68+ }
69+
70+ async function saveFavourites ( favourites ) {
71+ try {
72+ await fs . writeJson ( favouritesPath , { favourites } , { spaces : 2 } ) ;
73+ } catch ( error ) {
74+ console . error ( "Błąd zapisywania ulubionych:" , error ) ;
75+ }
76+ }
77+
5178// === TRASY INTERFEJSU WEB ===
5279app . get ( "/" , ( req , res ) => {
5380 const indexPath = path . join ( PROJECT_ROOT , "web" , "index.html" ) ;
@@ -104,11 +131,61 @@ app.use('/api/repos/:id/info', (req, res, next) => {
104131
105132// === API ROUTES ===
106133
107- // Lista repozytoriów
134+ // Lista repozytoriów (z ulubionymi)
108135app . get ( "/api/repos" , async ( req , res ) => {
109136 try {
110137 const repos = await listRepos ( ) ;
111- res . json ( repos ) ;
138+ const favourites = await loadFavourites ( ) ;
139+
140+ // Dodaj flagę isFavourite do każdego repozytorium
141+ const reposWithFavourites = repos . map ( repo => ( {
142+ ...repo ,
143+ isFavourite : favourites . includes ( repo . id )
144+ } ) ) ;
145+
146+ res . json ( reposWithFavourites ) ;
147+ } catch ( e ) {
148+ res . status ( 500 ) . json ( { error : e . message } ) ;
149+ }
150+ } ) ;
151+
152+ // Pobierz ulubione
153+ app . get ( "/api/favourites" , async ( req , res ) => {
154+ try {
155+ const favourites = await loadFavourites ( ) ;
156+ res . json ( { favourites } ) ;
157+ } catch ( e ) {
158+ res . status ( 500 ) . json ( { error : e . message } ) ;
159+ }
160+ } ) ;
161+
162+ // Dodaj do ulubionych
163+ app . post ( "/api/favourites/:id" , async ( req , res ) => {
164+ try {
165+ const repoId = req . params . id ;
166+ const favourites = await loadFavourites ( ) ;
167+
168+ if ( ! favourites . includes ( repoId ) ) {
169+ favourites . push ( repoId ) ;
170+ await saveFavourites ( favourites ) ;
171+ }
172+
173+ res . json ( { success : true , favourites } ) ;
174+ } catch ( e ) {
175+ res . status ( 500 ) . json ( { error : e . message } ) ;
176+ }
177+ } ) ;
178+
179+ // Usuń z ulubionych
180+ app . delete ( "/api/favourites/:id" , async ( req , res ) => {
181+ try {
182+ const repoId = req . params . id ;
183+ let favourites = await loadFavourites ( ) ;
184+
185+ favourites = favourites . filter ( id => id !== repoId ) ;
186+ await saveFavourites ( favourites ) ;
187+
188+ res . json ( { success : true , favourites } ) ;
112189 } catch ( e ) {
113190 res . status ( 500 ) . json ( { error : e . message } ) ;
114191 }
@@ -222,10 +299,17 @@ app.delete("/api/repos/:id/commit/:file", async (req, res) => {
222299// Usuwanie repo
223300app . delete ( "/api/repos/:id" , async ( req , res ) => {
224301 try {
225- const repoDir = path . join ( dataDir , "repos" , req . params . id ) ;
302+ const repoId = req . params . id ;
303+ const repoDir = path . join ( dataDir , "repos" , repoId ) ;
226304 if ( ! ( await fs . pathExists ( repoDir ) ) ) {
227305 return res . status ( 404 ) . json ( { error : "Repozytorium nie istnieje." } ) ;
228306 }
307+
308+ // Usuń również z ulubionych
309+ const favourites = await loadFavourites ( ) ;
310+ const newFavourites = favourites . filter ( id => id !== repoId ) ;
311+ await saveFavourites ( newFavourites ) ;
312+
229313 await fs . remove ( repoDir ) ;
230314 res . json ( { success : true } ) ;
231315 } catch ( e ) {
@@ -420,6 +504,53 @@ app.get("*", (req, res) => {
420504 res . sendFile ( indexPath ) ;
421505} ) ;
422506
507+ // Sprawdź czy plik jest tekstowy
508+ app . get ( "/api/repos/:id/checkfile/:commitFile/:filePath(*)" , async ( req , res ) => {
509+ try {
510+ const { id, commitFile, filePath } = req . params ;
511+ const zipPath = path . join ( dataDir , "repos" , id , "versions" , commitFile ) ;
512+
513+ if ( ! ( await fs . pathExists ( zipPath ) ) ) {
514+ return res . status ( 404 ) . json ( { error : "Snapshot nie istnieje." } ) ;
515+ }
516+
517+ const zip = new AdmZip ( zipPath ) ;
518+ const entry = zip . getEntry ( filePath ) ;
519+
520+ if ( ! entry ) {
521+ return res . status ( 404 ) . json ( { error : "Plik nie istnieje w archiwum." } ) ;
522+ }
523+
524+ // Sprawdź czy plik jest tekstowy
525+ const isText = isTextFile ( filePath ) ;
526+
527+ // Możemy też spróbować sprawdzić zawartość
528+ let isContentText = false ;
529+ try {
530+ const data = entry . getData ( ) ;
531+ // Próbujemy odczytać jako tekst UTF-8
532+ const text = data . toString ( 'utf8' ) ;
533+ // Sprawdzamy czy zawiera znaki kontrolne (oprócz typowych dla tekstu)
534+ const controlChars = text . split ( '' ) . filter ( c => {
535+ const code = c . charCodeAt ( 0 ) ;
536+ return ( code < 32 && code !== 9 && code !== 10 && code !== 13 ) || code === 127 ;
537+ } ) . length ;
538+ isContentText = controlChars / text . length < 0.1 ; // Mniej niż 10% znaków kontrolnych
539+ } catch ( e ) {
540+ isContentText = false ;
541+ }
542+
543+ res . json ( {
544+ isText,
545+ isContentText,
546+ size : entry . header . size ,
547+ filename : path . basename ( filePath )
548+ } ) ;
549+ } catch ( err ) {
550+ res . status ( 500 ) . json ( { error : "Błąd sprawdzania pliku: " + err . message } ) ;
551+ }
552+ } ) ;
553+
423554// Pomocnicze funkcje
424555function getContentType ( filename ) {
425556 const ext = path . extname ( filename ) . toLowerCase ( ) ;
@@ -445,8 +576,46 @@ function getContentType(filename) {
445576
446577function isTextFile ( filename ) {
447578 const ext = path . extname ( filename ) . toLowerCase ( ) ;
448- const textExtensions = [ '.txt' , '.js' , '.json' , '.html' , '.css' , '.md' , '.xml' , '.yml' , '.yaml' , '.php' , '.py' , '.java' , '.c' , '.cpp' , '.h' , '.cs' , '.rb' , '.go' , '.rs' , '.ts' , '.jsx' , '.tsx' , '.vue' , '.svelte' ] ;
449- return textExtensions . includes ( ext ) ;
579+ const textExtensions = [
580+ '.txt' , '.js' , '.json' , '.html' , '.css' , '.md' , '.xml' , '.yml' , '.yaml' ,
581+ '.php' , '.py' , '.java' , '.c' , '.cpp' , '.h' , '.cs' , '.rb' , '.go' , '.rs' ,
582+ '.ts' , '.jsx' , '.tsx' , '.vue' , '.svelte' , '.sql' , '.ini' , '.cfg' , '.conf' ,
583+ '.log' , '.sh' , '.bash' , '.zsh' , '.fish' , '.mjs' , '.cjs' , '.env' , '.gitignore' ,
584+ '.dockerignore' , '.editorconfig' , '.prettierrc' , '.eslintrc' , '.babelrc' ,
585+ '.npmrc' , '.yarnrc' , '.gitattributes' , '.gitmodules' , '.htaccess' , '.env.example' ,
586+ '.env.local' , '.env.development' , '.env.production' , '.env.test'
587+ ] ;
588+
589+ // Sprawdź po rozszerzeniu
590+ if ( textExtensions . includes ( ext ) ) {
591+ return true ;
592+ }
593+
594+ // Sprawdź czy to plik ukryty (z kropką na początku nazwy)
595+ const fileName = path . basename ( filename ) . toLowerCase ( ) ;
596+ if ( fileName . startsWith ( '.' ) && fileName . length > 1 ) {
597+ // Wyjątek: niektóre pliki binarne mogą mieć kropkę, ale są binarne
598+ const binaryHiddenFiles = [ '.DS_Store' , '.localized' ] ;
599+ if ( binaryHiddenFiles . includes ( fileName ) ) {
600+ return false ;
601+ }
602+ return true ;
603+ }
604+
605+ // Sprawdź czy to znany plik tekstowy bez rozszerzenia
606+ const knownTextFiles = [
607+ 'dockerfile' , 'makefile' , 'procfile' , 'docker-compose.yml' , 'docker-compose.yaml' ,
608+ 'docker-compose.override.yml' , 'package.json' , 'package-lock.json' , 'yarn.lock' ,
609+ 'composer.json' , 'composer.lock' , 'gemfile' , 'gemfile.lock' , 'cargo.toml' ,
610+ 'cargo.lock' , 'go.mod' , 'go.sum' , 'pom.xml' , 'build.gradle' , 'build.gradle.kts' ,
611+ 'settings.gradle' , 'settings.gradle.kts' , 'gradle.properties' , 'gradle-wrapper.properties'
612+ ] ;
613+
614+ if ( knownTextFiles . includes ( fileName ) ) {
615+ return true ;
616+ }
617+
618+ return false ;
450619}
451620
452621// === Start serwera ===
@@ -467,5 +636,6 @@ app.listen(PORT, "0.0.0.0", () => {
467636 console . log ( "\n🚀 mygit uruchomiony!" ) ;
468637 console . log ( `🌍 http://${ localIp } :${ PORT } ` ) ;
469638 console . log ( `📁 Katalog danych: ${ dataDir } /repos/` ) ;
639+ console . log ( `💖 Plik ulubionych: ${ favouritesPath } ` ) ;
470640 console . log ( `🕓 Start: ${ dayjs ( ) . format ( "DD.MM.YYYY | HH:mm:ss" ) } \n` ) ;
471641} ) ;
0 commit comments