MVC 4! + Angular2 rooting - połączenie 2 autostrad

Trochę się zakopałem ostatnio ale na rozgrzewkę mam temat z ostatniej przygody. 
Jestem zadziorny i jak wszyscy .Net robią Angular2 z MVC 5, to w tym projekcie che pogodzić Angulara z MVC 4. 

Specyfika projektu to:

  • /front/*  - to Angular2 dostępny tylko dla zweryfikowanych
  • !/front/* - MVC wraz z weryfikacją.

Angular oczywiście będzie pracował na własnym zarejestrowanym routingu, MVC też, i rozgraniczenie tego na czystych wpisach do MapRoute działało dobrze jeśli użytkownik wszedł na stronę logowania a następnie korzystał z aplikacji. Jeśli jednak posiadał linka /front/xza, a nie był zweryfikowany otrzymywał 404. Brutalne :) Brakowało małęgo RedirectTo /Login?ReturnUrl=%2ffront%2fxza  Brutalne :)

Spróbowałem więc na poziomie rooting odfiltrować.
Klasa do zarejestrowania Constraint

 public class ServerRouteConstraint : IRouteConstraint
 {
     private readonly Func<Uri, bool> _predicate;

     public ServerRouteConstraint(Func<Uri, bool> predicate)
     {
         this._predicate = predicate;
     }

     public bool Match(HttpContextBase httpContext, Route route, string parameterName,
         RouteValueDictionary values, RouteDirection routeDirection)
     {
         if(routeDirection == RouteDirection.IncomingRequest)
             return this._predicate(httpContext.Request.Url);
         return true;
     }
 }

I zarejestrowanie 

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new
            {
                serverRoute = new ServerRouteConstraint(url =>!isFront(url))
            }
    );
    routes.MapRoute(
        name: "angular",
        url: "{*url}",
        defaults: new { controller = "Home", action = "FrontIndex" },
        constraints: new
        {
            serverRoute = new ServerRouteConstraint(url => isFront(url))
        }
    );

}

private static bool isFront(Uri url)
{
    return url.PathAndQuery.StartsWith("/front",StringComparison.InvariantCultureIgnoreCase) ;
}

Ale tutaj mamy mały problem, gdyż moment wywołania isFornt nie posiada obiektu Sessji. Z tego powodu specjalnie akcja domyślnie jest przekierowywana pod akcje FrontIndex w kontrolerze Home. Tam jest weryfikacja do której również jest potrzebna sesja, i w zależności od tego albo jest wczytywany Index gdzie pałeczkę przejmuje Angular, albo path jest przekazywany do strony logowania.

[HDAuthorization]
public ActionResult Index()
{
    ViewBag.Title = "Use angular and have fun";
    return View("Index");
}

public ActionResult FrontIndex()
{
    if (HDAuthorization.IsLoginUser())
        return Index();
    else
    {
        return RedirectToAction("Login", new
        {
            ReturnUrl = Request.Path
        });
    }
}

public ActionResult Login()
{
    ViewBag.Title = "Login Page";
    return View();
}

[HttpPost]
public ActionResult Login(LoginModel model)
{
    var loginReturn = loginManager.Login(model);
    if (loginReturn.Authenticated)
    {
        if (Request.Path.StartsWith("/front/"))
            return Redirect(Request.Path);
        string requestParam = Request.Params["ReturnUrl"];
        if (string.IsNullOrWhiteSpace(requestParam))
            return  RedirectToAction("Index");
        return  Redirect(requestParam);
    }
    ViewBag.LoginError = loginReturn.FailureText;
    return View();
}

Oczywiście dla angulara konfiguracja wygląda następująco : 

const appRoutes: Routes = [
    { path: 'front/requests', component: AppRequests },
    { path: 'front/request/:id', component: AppRequest } ......

I udało się. Może nie top 100 najładniejszych rozwiązań ale spokojnie wystarcza mi odseparowanie path /front/* dla Angulara a pozostałe niech pożera MVC, natomiast co najważniejsze kiedy user nie ma dostępu do /front/ bo nie jest jeszcze zweryfikowany to dostanie redirect do login wraz z linkiem powrotnym do frontu. 

 

via GIPHY

#4Developers i powrót do RL

Ahh. Już w domu, szkoda. 
Emocje opadły, wiedza jeszcze się indexuje xD ale Wartało!

Cała ścieżka .Net przepleciona z małym odskokiem follow by @konradkokosa

No ale po kolei:

Pierwsze Before PARTY YEaah. Jadąc z Jasła, byliśmy pewni że wpadniemy do klubu i nie będzie już gdzie siąść. Ale udało się wbić na free browar dla pierwszej setki, 43 byłem zeskanowany :). Jakoś w dziwnych okolicznościach mykły 4 browce i cała impreza praktycznie przeniosła się na zewnątrz. Oprócz przechadzającego się gandalfa, najważniejszy był wodzirej @j_palka, który nawoływał do wspólnej integracji wymownymi cytatami "Pijemy, Pijemy, Pijemy!" :) Funn na maxa.

F# -> C#  @bartsokol

Trzeba przyznać się, że głowa z rana jeszcze pulsowała, ale za to Bartek przygotował dosłownie prezentacje w sam raz na rozgrzewkę. I w brew pozorom nie był to jakiś low level. Gość pokazał jak za pomocą 2 klas szablonowych(Result<T> i Optional<T>) i 2 komend(Map, Bind) zbliżyć składnię F# do C# i POSPRZĄTAĆ!.
Coś pięknego. Zamiast skakanie po blokach if-ów i kolejnych zagnieżdźeniach, można bardzo przejżyście i zrozumiale Biznesowo utrzymać kod przynajmniej w głównej akcji.

Przykład jak wyglądała by taka zmiana niku przeze mnie:

public Response ChangeNick(ChangeNickModel model)
    {
        try
        {
            string error = UsersManager.ValidModel(model);
            if(string.IsNullOrWhiteSpace(error))
                return new { Error = "Istnieje inny user z takim nickiem", Succes = false };
            using (var db = new DbContext())
            {
                var user = UsersManager.GetUserModel(model.nick,db);
                if (user != null)
                {
                    if (UsersManager.CheckNickIsFree(model))
                    {
                        if (UsersManager.ValidPassword(user.nick, user.pw))
                        {
                            UsersManager.SetNewNick(user.nick, user.newNick);
                            EmailManager.SendNewNick(user);
                            var newUserToStore =  UsersManager.GetUser(user.newNick);
                            return new {Succes = true, NewUserToStore = newUserToStore};
                        }
                        else
                        {
                            return new { Error = "Błędne hasło", Succes = false };
                        }
                    }
                    else
                    {
                        return new { Error = "Istnieje inny user z takim nickiem", Succes = false };
                    }

                }
                else
                {
                    return new { Error = "Błędy user", Succes = false };
                }
            }
        }
        catch (Exception)
        {
            return new { Error = "Z przyczyn bliżej nam nie znanych nie możemy ci pomuc", Succes = false };
        }
    }

W sumie operacja nie jest skomplikowana ale trochę zajmuje.

Kod by Bartek byłby *Uwaga mogę coś pokiklać ale mam : 

public Response ChangeNick(ChangeNickModel model)
    {
        return model.Map(UsersManager.CreateChangeNickContext)
            .Map(UsersManager.ValidModel)
            .Bind(UsersManager.GetUser)
            .Bind(UsersManager.CheckNickIsFree)
            .Bind(UsersManager.ValidPassword)
            .Bind(UsersManager.SetNewNick)
            .Bind(EmailManager.SendNewNick)
            .Map(context => new { Succes = true,NewUserToStore = context.User});
    }

Troche bardziej czytelnie niee ? :D Fakt faktem, że do tego potrzebne są te klasy i metody, które miał Bartek na prezentacji. Więc jak je udostępni to podrzucę je tutaj.(EDIT JEST !: https://speakerdeck.com/bartsokol/functional-developer-in-object-oriented-world)
Oczywiście, ktoś powie, że więcej kodu pewnie się napisze bo np zrobimy klasę kontekstu zmiany niku, ale jednak jak wróci się do tego kodu po 9 miesiącach to odrazu wiadomo co jest wykonywane. A dodam ze haczykiem tych klas szablonowych jest to. że jeśli wystąpi błąd na np 2 kroku to jego propagacja zostanie- przeleci przez pozostałe metody lecz bez ich wywoływania. ILE MIE IFÓW   Óffff.

Flow wątków w TPL by @maklipsa

Kiedyś myślałem że korzystam z TPL, ale to co pokazał Szymon jest kozackie. Drugi raz jestem na prelekcji Szymona i gość gasi moje pragnienie na content. Boje się iść na 3 prezentację xD.  Ale do content-u, to że Taski są jednym z debeściackich rzeczy w .Net i to że bardzo łatwo się ich używa to nie wiedziałem, że za pomocą TPL można połączyć te wszystkie Taski w uporządkowane flow z bloków, a cała kwintesencja jest w tym iż każdy z bloków może posiadać swoją kolejkę do wykonania, w ilu Taskach będzie ją realizować, Ile wiadomości per Task, i co najważniejsze ile wiadomości w jednym czasie, czyli po mega rozgałęzieniu można zejść do 1 taska gdzie będzie obsługa czegoś mega nie Thread Safty jak SQL tak jak to ma miejsce u Szymona.

Polecam mega przestudiować jego prezentacje na ten temat i oczywiście Dać Stara github.com/maklipsa/donetconfpl_tpldataflow

ETW  & Memento Memori by master of Memory @konradkokosa

Konrada to bym mógł słuchać godzinami. Dwie prelekcje, pierwsza o narzędziu ETW, które jest jest od dawna a nie wyparło logowania do pliku :), pomimo iż nadaje 0 narzut na produkcje i załączenie/wyłączenie logów nie wymusza jakiegokolwiek wstrzymania Serwisu/Witryny/Programu/Serwera. Natomiast ma ono tyle możliwości być toolem zarówno do monitorowania performance naszej apki jak i jej zdarzeń. Wychodzi na to że żadne memory profilery nie są potrzebne a z ETW korzystamy na co dzień w EventLog(Rejestr zdarzeń) i Monitor zasobów. 

Natomiast druga prezentacja to kwintesencja starej dobrej zasady, "Dopuki nie wiesz jak to działą, to nie wiesz co się dzieje". Trzeba znać choć podstawową wiedzę aby posługiwać się sprawnie narzędziami. I tak na podstawie ciekawych przykładów Konrad pokazał jak np. cache L3 zrobi nam psikusa.  Zgłębianie tematów wydajności to coś za czym można podążać za Konradem na jego blogach : tooslowexception.com  i blog.kokosa.net

Try use Serverless in @gutek live 

Nie wiem czy to tradycja u gutka, ale również w tamtym roku, przygotował podczas prezentacji przerywnik typeof(Exception). Zapowiedział że tak będzie, więc żadnej wtopy nie było. Za to bardzo fajnie porównał rozwiązanie Azure i Amazon. Jedno wiem na pewno, tym trzeba się pobawić i cieszy fakt że jest to za free do po testowania azure.microsoft.com/en-us/services/functions/  a na prd jest limit 10 000 req/m  ? Coś takiego.

The only thing that matters @Scooletz

Pierwszy raz na prezentacji Szymona, i jednak muszę powtórzyć temat na rzemioslo.it gdzie będzie ta prezencja. Swoją drogą zapraszam do RZESZOWA :D 

 

Natomiast pozostałe 3 prezentacje jeszcze się procesują w mojej głowie. Jednego jestem pewien, POLECAM jak najbardziej 4developers. A jeszcze bardziej polecam do śledzenia tych prelegentów, bo ich wiedza na festiwalu to strzał w głowę nową energią, a żeby zgłębić ich wiedzę zachęcam do ich blogów :)

 

A teraz powrót do realu :D 

Znajdź najkrótszą ścieżkę i do domu

Znajdź najkrótszą ścieżkę i do domu

Magiczna komenda jaką daje nam silnik i pozamiatane. Nie no co wy, zwróci nam najkrótszą drogę ale po ilości relacji.

MATCH (s:Point),(e:Point),
p = shortestpath((s)-[*]-(e))
WHERE s.name = 'JASŁO' AND e.name = 'RZESZÓW'
return p

No ale nam chodzi o odległość, a posiadamy strukturę punktów osadzonych na kawałkach drogi, a poruszamy się między punktami, droga to tylko medium. Więc w głowie pojawia się pomysł bezpośredniego połączenia punktów.

MATCH (s:Point)-[r:Stay]-(t:Segment)
where  r.lineNo = t.lineNo
with distinct s,r.km as km, t.lineNo as  lineNo
order by km 
with collect({s:s,km:km,lineNo:lineNo}) as one , collect({s:s,km:km,lineNo:lineNo}) as two
UNWIND one as ss 
with  ss  , filter(next IN two WHERE ss.lineNo = next.lineNo and next.km >= ss.km and NOT next.s.name = ss.s.name)[0] as nss
with  ss.s as fi,  nss.km-ss.km as distance , ss.lineNo as lineNo, nss.s as se
where se is not null
merge (fi)-[:Connect{distance: distance ,lineNo: lineNo}]-(se)

Przy tym skrypcie się już trochę rozpoznałem Cypher'a, ale jeśli ktoś ma inny pomysł na połączenie tego to proszę bardzo :).

Mając na połączone punkty no to tylko sumować odległości dzieki reduce i dawaj najkrótszą. 

START  startNode=node:node_auto_index(name="Start"),
             endNode=node:node_auto_index(name="Finish")
MATCH  p=(startNode)-[:NAVIGATE_TO*]->(endNode)
RETURN p AS shortestPath,
                reduce(distance=0, r in relationships(p) :  distance+r.distance) AS totalDistance
                ORDER BY totalDistance ASC
                LIMIT 1;

I lepiej tego nie puszczać jeśli się ma więcej niż 100 nodów. XD Gdy rozpoczeła się walka z brakiem RAM i mając już 5 min zastanawiania się dlaczego to tak długo, zrozumiałem że gość tak naprawdę robi iloczyn kartezjański i wszystko próbuje obliczyć... Porażka, Jak takie zajefajne grafy i na 25k Pointów i 60k odcinów się wysypuje. 

No ale 90% problemu to brak zrozumienia jak to działa. I oczywiście każdy miał na studiach algorytmy i było o takim na D, co tak szybko szuka. 

Daleko szukać i głowić się nie trzeba, jest już zaimplementowany jako plugin do neo, neo4j-apoc-procedures

MATCH (s:Point{name:'JASŁO'}),(e:Point{name:'RZESZÓW'})
CALL apoc.algo.dijkstra(s, e, 'Connect', 'distance') YIELD path, weight
RETURN path, weight

No i to już jest faktyczna prawdziwa najkrótsza ścieżka.

Teraz temat co jeśli nie możemy jechać przez np Czudec.  Na szybko mam tylko taki pomysł żeby skopiować relacje które są dostępne czyli bez Czudca, a następnie wyszukać połączenie, i oczywiście pamiętać żeby to chwilowe powiązanie usunąć.

MATCH (s:Point)-[c:Connect]-(t:Point)
where s.name <> 'Czudec' and t.name <> 'Czudec' 
merge (s)-[:Connect_tempWOCzud{distance: c.distance ,lineNo: c.lineNo}]-(t)

Jak sprawdziłem skopiowanie 2k relacji bez 2 bo akurat tyle miał Czudec to 450ms, więc jeszcze nie tak źle. Gorsza jest trasa bez przejazdu przez Czudec gdzie okazuje się że muszę nadgonić 100km aby go ominąć. Taką mamy mapę :)

MATCH (s:Point{name:'JASŁO'}),(e:Point{name:'RZESZÓW'})
CALL apoc.algo.dijkstra(s, e, 'Connect_tempWOCzud', 'distance') YIELD path, weight
RETURN path, weight

 

Próbując wytyczyć alternatywne ścieżki okazało się że dane są nie dokładne a najlepszy błąd to stworzenie punktu Granica Państwa gdzie z odległością 0 można się teleportować z Zgorzelca do Cieszyna :) 

Tyle eksperymentów z czystym Neo, następny krok to kawałek, który będzie miał za zadanie obsługę obsługę całej transakcji bez wyłączonych odcinków itd.

I DO DOMU :D 

 

A Cypher syntax highlighter for SyntaxHighlighter js

HERE https://gist.github.com/sakrut/cef1a335ff6f778eda809d75527dddd9

Tak irytowała mnie monotonia w kodzie w Cypher jaki wyrzucałem na bloga, że nie mogłem się oprzeć zmianie togo.
Przerwałem pisanie posta i korzystając z sygnatur do Notepad++  https://gist.github.com/nicolewhite/b0344ea475852c8c9571 zrobiłem pod swojego weba.

Pamiętajcie że odczucie koloru jest względne :) ale czekam na wszelkie sugestie jeśli coś brakuje.

A to commity jak gdzie to wpiąć:

https://github.com/sakrut/BlogEngine.NET/commit/3b7b0ccb2b81ddf181a7a22c44ef64d5cf6e6953

https://github.com/sakrut/BlogEngine.NET/commit/aceb2dbafac6af9e56bb1bf4c14b362e775d2aa5

 

Demo xD :

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///pointLocation.csv" AS row
MATCH (p:Point {name: row.nazwa })
MATCH (stc:Segment {lineNo: toInt(row.nr_lini)})
Where toInt(row.km_os) >= toInt(stc.kmFrom) and (toInt(stc.kmTo) >  toInt(row.km_os) or toInt(row.km_os) = toInt(stc.kmTo))
MERGE (p)-[:Stay { km: toFloat(row.km_os),  lineNo: toInt(row.nr_lini) }]-(stc);

 

 

Pierwsze rysowanki - E to Graf jest

Hehe powrót do przedszkola normalnie. Ale ten feature jest zajebisty :) ktoś za to powinien dostać 1mln$. Gdyby nie taka wizualizacja to pewnie długo bym siedział przy SQL i jazda jak exel.

Na grafach znam się tyle co teoria w 2 linijkach na sprawdzianie. Natomiast ostatnio podczas rg-dev w Rzeszowie była prelekcja Szymona Warda @maklipsa dotycząca grafów. Fajnie wszystko opowiedział, i widać z pasją i zaangażowaniem, ale z przyczyn filipińskich i późnej pozy to miałem content w głowie ale microservice od analizy informacji już leżał. Jedyne co zwrócił mi. :)

Cola + Ryba => "Cooo...????"

I tak po 2 dniach serwis wstał i zwraca: że te grafy to są błeee, bo gdzie tu not Nulle, gdzie tu spójność, biznes w to z trudem bo do exela będzie trudno. Nie mija 5 min i w tej czasce zaświeciła się lampka: "Przecież to jest jak mapa połączeń". Ha odkrywcze nie ? Normalnie geniusz. W definicji tak pisze. "Ale nieee, nieee, nie ze same połączenia, to jest baza danych gość mówił. To normalnie się JE, to jakiś ma język zapytań na 5 piętrze. To to będzie dobre bo to da się zrobić inaczej joiny.".

No i właśnie do roboty. Szymon mówił o jakimś silniku jak rybka. Neo.... Neo4j. https://neo4j.com/ 
Powiem tyle. Jest ZajeEEEEEE ty, przez tą wizualizację. To nie to co Młotek do sql'a. aż się chce wpisywać Match (s) return s.  Normalnie byle by RAM w chromie starczył bo jednak przy wielu elementach można sobie zaszkodzić.

Import danych   

https://neo4j.com/developer/guide-import-csv/  
https://neo4j.com/developer/guide-importing-data-and-etl/

Przecież z marszu nie będziemy robić mam jakieś dane odnośnie komunikacji to sobie z SQL wyciągnę. Ale tu jest haczyk. Jak 5 lat romantycznie spędzałeś z SQL, tak teraz ciężko obrobić dwie kochanki i najlepiej całe "żarcie"(dane) dla nowej zrobił byś w kuchni starej dziewczyny. Ehh Ale dobre porównanie, dosłowne. Pierwsze co to zamiast importować to zaczynają się triki, a to może zrobię 2,3..8 joinów 7 subSelect i jazda. GŁUPOTA!

Nowa kochanka to też silnik bazodanowy, mając dane 1:1 w strukturze sql-csv, wystarczy nauczyć rozmawiać w nowym języku.

Pierwszy najważniejszy skrypt: 

MATCH (n) DETACH
DELETE n;

Trudno się nie domyślić, jest najlepszy bo za każdym razem puszczamy go ostatni raz. :)

Ale dobra importujemy jeden label(to te kropki-ala tabela po staremu), drugi. - Odcinki drogi (Segment), Przystanki/Punkty docelowe (Pointy)

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///Segment.csv" AS row
CREATE (:Segment {lineNo: toInt(row.lineNo) ,speedLimit: toInt(row.speedLimit),relationSort: toInt(row.relationSort) ,kmFrom :  toFloat(row.kmFrom),kmTo : toFloat(row.kmTo)});

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///point.csv" AS row
CREATE (:Point {name: row.nazwa   });

O ile Segmenty są maja swoją kolejność i można bardzo łatwo połączyć w jedną linie. A tak naprawdę dla każdej linii można było by zrobić label.

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///tracks2.csv" AS row
MATCH (sta:Segment {lineNo: toInt(row.lineNo),relationSort: toInt(row.relationSort)})
MATCH (stc:Segment {lineNo: sta.lineNo,relationSort: (sta.relationSort+1)})
MERGE (sta)-[:Continue]-(stc);

Ale to to pikuś, problemem w SQL było napisanie takiego skryptu aby segmenty połączyć z Poitami. Udało mi się z innych źródeł złapać informacje jaki Pint(nazwa) na jakim jest km linii(lineNo), i od tego momentu to byłą bajka w połączeniu(ten pierwszy skrypt uruchomiłem 30 razy i taki poszło):

USING PERIODIC COMMIT
LOAD CSV WITH HEADERS FROM "file:///pointLocation.csv" AS row
MATCH (p:Point {name: row.nazwa })
MATCH (stc:Segment {lineNo: toInt(row.nr_lini)})
Where toInt(row.km_os) >= toInt(stc.kmFrom) and (toInt(stc.kmTo) >  toInt(row.km_os) or toInt(row.km_os) = toInt(stc.kmTo))
MERGE (p)-[:Stay { km: toFloat(row.km_os),  lineNo: toInt(row.nr_lini) }]-(stc);

import.done.pl

Znajdź najkrótszą ścieżkę i do domu

Magiczna komenda jaką daje nam silnik i pozamiatane. Nie no co wy, zwróci nam najkrótszą drogę ale po ilości relacji.

MATCH (s:Point),(e:Point),
p = shortestpath((s)-[*]-(e))
WHERE s.name = 'JASŁO' AND e.name = 'RZESZÓW'
return p

Reszta suuuun. bo dziś Dzień kobiet więc "Panowie Zdrowie Pań". BACZNOŚĆ DO OBOWIĄZKÓW.
Panie: Wszystkiego najlepszego.

 

WebRTC-skype? a może coś więcej

Może kolejny komunikator wideo to nie najlepszy pomysł ale wykorzystanie WebRTC może zaspokoić potrzeby biznesu i Helpdesków. :) 

https://www.youtube.com/watch?v=ocg5HN0nt-k

 Kod

https://github.com/sakrut/webrtc-and

https://github.com/sakrut/webrtc-api

 Lepsze strony

https://github.com/GleasonK/android-webrtc-tutorial

https://webrtc.org/start/ 

https://webrtc.github.io/samples/

https://www.html5rocks.com/en/tutorials/webrtc/basics/

 

Daj się poznać 2017

dajsiepoznac.pl

Oto i cały inicjator prowadzenia bloga.

No nic założenia są proste.
Pomysł na Opena to coś, co pomoże złączyć .Neta z Grafami na tyle aby można było wykorzystać to do rozwiązywania problemu komiwojażera.

UUU student ktoś powie. Nic podobnego. Wiem ze w czasach wyznaczania trasy przez Google czy Janosika takie problemy są 300* rzędne, lecz w świecie jakim żyje mój produkt problem ten nadal istnieje i nie znalazłem** narzędzi do jego rozwiązania.

Nie mam ustalonego planu projektu czy też technologi jakich ostatecznie otrzymam. Wiem tylko że będę chciał to w końcowej fazie doprowadzić otrzymać w WinFORMSACH. TAAAAA :D - A czemu nie.

 

Mam nadzieje że przynajmniej moje próby wypocenia się zostaną odebrane pozytywnie.

 

ps. *Nie lekceważ 300 :D  
     ** No sory czasami Ctrl-F i google zawodzi.

 

 

:

No to lecimy 4.3.2.1 Start

Uff. Start. Jak to dobrze ze default theme jest Zaje!
Uff ? na starcie? Teraz wzdycham bo potem pewnie jak zawsze braknie czasu na wzdychanie.

Czas zacząć pisać, eeee nie. Szkoda pisać przecież kodujemy/piszemy cały dzień. Czas coś pewnie nagrać. 

Tak więc zapraszam do oglądania i konstruktywnej krytyki. 

A przedstawić się jeszcze zdążę :)