Commit 46f24d4b authored by Johan Vervloet's avatar Johan Vervloet

GAP-wiki gemigreerd naar source code.

parent 63baaf5f
# GAP
Het GAP is het GroepsAdministratieProgramma van de Chiro.
Het GAP is het GroepsAdministratiePortaal van de Chiro.
De officiële source code en issue tracker zijn hier gehost:
https://websites.chiro.be/projects/gap
De officiële source code en issue tracker zijn gehost op onze
[gitlabserver](https://gitlab.chiro.be/gap/gap). De
[doc/WikiStart.md](documentatie voor ontwikkelaars) was vroeger een
wiki, maar staat nu gewoon bij in de source code.
Er staat een [kloon van de repository](https://github.com/Chirojeugd-Vlaanderen/gap)
op GitHub, voor de mensen die het gewend zijn pull requests te maken.
op GitHub, voor diegenen die geen Chiro-account hebben, en zich toch
aan een pull request willen wagen.
Als je in de issue tracker wilt werken, moet je aanloggen met
je Chiro-account. Aanloggen gebeurt
[via CAS](https://websites.chiro.be/cas?ref=%2Fmy%2Fpage).
Als je in de issue tracker wilt werken, moet je in gitlab aanloggen
met je Chiro-account. Hiervoor klik je op de
[aanmeldpagina](https://gitlab.chiro.be/users/sign_in) op de link 'CAS'.
Als je geen Chiro-account hebt, contacteer dan
[de helpdesk](https://chiro.be/eloket/feedback-gap), en vermeld dat
......@@ -18,6 +21,4 @@ je graag een account hebt voor de issue tracker van het GAP. Als je
roots hebt in de Chiro, vermeld dan ook de groep waarin je actief
bent geweest.
De [developer wiki](https://websites.chiro.be/projects/gap/wiki) bevat
wat meer uitleg over hoe je een dev-versie van het GAP draaiende krijgt
op je eigen PC.
Release! Alwat nog niet af is, gaat naar RC2.
Er is geen nieuwe release geweest n.a.v. deze nieuwe milestone, o.w.v.
problemen met de routers net voor mijn vakantie. Nu lijkt het me wat
verloren moeite om nog een release uit te rollen met
bivakaangiftegerelateerde problemen. (Voor de meeste groepen is de
aangifte nu in orde.) De openstaande zaken worden verhuisd naar 1.2.
Bivakaangifte in orde voor GAP. Sync naar Kipadmin ontbreekt. Sync en
een paar simpele bugfix zullen we met 1.1.1 releasen.
Startveradering 27/09/2012
==========================
Voorgestelde planning GAP
Release 1.5
-----------
\* Releasen rond de winter\
\* Fixen aanslepende issues\
\* Overzicht: version\#16
Grotere projecten
-----------------
\* Releasedatum onbekend, alnaargelang tijd en interesse\
\* API: \#845\
\* 'User Experience'\
\* Refactoring Backend: [ArchitectuurProblemen](ArchitectuurProblemen.md)\
\* Infrastructuur\
**** Security message queues: \#927\
**** Migratie SQL server: [MigratieSqlServer](MigratieSqlServer.md)
Vrijwilligersinzet
------------------
\* De vrijwilligers programmeren aan wat ze interessant vinden\
\* Fixes van issues uit version\#16 worden in december gereleaset\
\* Andere zaken pas in release 1.6 of later
API
===
REST-API
--------
Zie \#3283.
### Moeilijkheden met authenticatie
Als de frontend de backend aanspreekt, dan authenticeert de user zich
via basic auth tegenover AD. 'Impersonation' zorgt ervoor dat de
frontend de backend aanspreekt alsof de frontend die aangelogde
gebruiker is.
Voor de API zou het leuk zijn moest er met tokens gewerkt kunnen worden
(zoals bijv. OAuth). Probleem is, dat als de api aangeroepen wordt met
een token, er geen voor de hand liggende manier bestaat om de
overeenkomstige user te 'impersonaten'.
Misschien kan dat wel, en moeten we nog uitvissen hoe.
Misschien kan het niet. In dat geval kan Chiro.Gap.Services opgesplitst
worden in Chiro.Gap.Services1 en Chiro.Gap.Services2 (maar dan met
betere namen).\
Chiro.Gap.Services1 zoekt dan voor elke call uit 'wie ben ik'?, en geeft
die informatie als dusdanig door aan dezelfde call in
Chiro.Gap.Services2, maar dan met 1 extra parameter (de user).
De API zoekt dan ook uit 'wie ben ik,' maar aan de hand van een token,
en kan dan ook Chiro.Gap.Services2 gebruiken.
OData-API
---------
De OData-API zal terug verdwijnen. Zie [OData-API](OData-API.md).
Aanwezigheden
=============
vrijdag & ontbijt
-----------------
- bart
- janec
- tim
- tim
- pieter
- johan
- ben
- geert s.
- sam
- edward
- wouter
zaterdag voor de middag
-----------------------
- maarten
- niels
- jana
- steven
- wouter
- bart
- janec
- tim
- tim
- pieter
- johan
- ben
- geert s.
- sam
- edward
- jo
middageten
----------
- jana
- steven
- wouter
- bart
- janec
- tim
- tim
- pieter
- johan
- ben
- geert s.
- sam
- edward
- jo
eten zaterdagavond
------------------
- tim
- tim
- pieter
- johan
- ben
- geert s.
- sam
- willem
zondag
------
- tim
- tim
- pieter
- johan
- ben
- geert s.
- sam
- willem
Aanzet API
==========
(zie [IntroductieWcfDataServices](IntroductieWcfDataServices.md) voor een korte uitleg over de
gebruikte technologie)
Gebruiken
---------
(Ja hoor, hier vind je voorbeelden voor de api:)
Pak de solution uit, en zet Chiro.Gap.WebApi als startup project. Als je
de solution start, dan krijg je deze pagina te zien:
http://localhost:62225/
(het poortnummer kan verschillen) met daarop een foutmelding. Dat is
geen probleem, pas de url als volgt aan:
http://localhost:62225/api/Persoon
en je krijgt een overzicht van alle gelieerde personen van je groep.
Zoals gezegd kun je out of the box query'en via de url:
- http://localhost:62225/api/Persoon(89183) (persoon met
[GelieerdePersoonID](GelieerdePersoonID.md) 89183)
- http://localhost:62225/api/Persoon?\$filter=Naam%20eq%20'Peeters' (filteren)
- http://localhost:62225/api/Persoon?\$orderby=Voornaam (sorteren)
- http://localhost:62225/api/Persoon?\$skip=20&\$top=10 (paginering)
Andere interessante query's:
- http://localhost:62225/api/Adres(81923)/Personen (alle personen die
op een adres wonen)
- http://localhost:62225/api/Afdeling(5222)/Personen (alle personen
uit een afdeling)
Versies
-------
Personen, adressen en contactinfo krijgen een versie mee. Dat is een
soort van hexadecimale string die aangeeft hoe recent informatie is.
Informatie met een hogere versie is recenter dan informatie met een
lagere versie.
Architectuur
------------
De API werkt op IQueryables, dus we kunnen Chiro.Gap.Services niet
zomaar gebruiken. Op dit moment werkt de API rechtstreeks op de data
access in de backend. Maar niet zonder problemen, zie \#2774.
Datacontracts
-------------
De datacontracts zijn gedefinieerd in Chiro.Gap.WebApi/Models.
Opmerking
---------
Als je \$format=json toevoegt aan je query, dan zou je json moeten
krijgen. Dat werkt momenteel niet, vermoedelijk een configuratie issue.
Zie ook
-------
- [API](API.md)
Agenda voor de vergaderingen
============================
\* [20120927](20120927.md) - startvergadering
Mappen van entiteiten naar DataContracts via [AutoMapper](AutoMapper.md)
===============================================================
Waarom
------
De GAP user interface communiceert met de businesslaag via services. Als
er voor de user interface een lijst met namen en afdelingen nodig is,
dan moet die informatie geserialiseerd over de lijn.
Onze business objects zijn entity's van het Entity Framework. Voor een
lijst met naam en afdeling informatie heb je bijgevolg informatie nodig
uit een graaf, bestaande uit entiteiten Persoon, GelieerdePersoon, Lid
en Afdeling. Zo'n geserialiseerde graaf wordt al gauw vrij groot, wat
niet interessant is.
Vandaar dat we voor dit soort lijsten speciale Data Contracts
definieerden, zoals bijvoorbeeld LidInfo. (Zie
\[source:trunk/Solution/Chiro.Gap.ServiceContracts/DataContracts/LidInfo.cs\].)
Om zo'n graaf van Entiteiten (bijv. gekoppeld aan lid) zonder te veel
programmeerwerk te kunnen mappen naar een compacter datacontract (bijv.
LidInfo), gebruiken we [AutoMapper](AutoMapper.md).
Gebruik
-------
Stel dat je een rij gelieerde personen wil mappen naar een lijst met
PersoonInfo. Leg dan een reference naar
\[source:trunk/Solution/References/AutoMapper.dll\] in je project, en
voorzie deze using:
<pre>\
using [AutoMapper](AutoMapper.md);\
</pre>
Het mappen zelf, gebeurt dan als volgt:
<pre>\
IEnumerable<GelieerdePersoon> gelieerdePersonen;
// zorg ervoor dat gelieerdePersonen opgevuld geraakt
IList<PersoonInfo> infoVanPersonen =
Mapper.Map<IEnumerable<GelieerdePersoon>,
IList<PersoonInfo>>(gelieerdePersonen);\
</pre>
De eerste generieke parameter van Mapper.Map is het 'Brontype', de
tweede is het 'Doeltype', en Mapper.Map zet dus een object van het
brontype om in het doeltype.
Je vindt hiervan allerlei voorbeelden in
\[source:trunk/Solution/Chiro.Gap.Services/GelieerdePersonenService.svc.cs\]
Configuratie
------------
De configuratie van de mapping gebeurt in de statische functie
Chiro.Gap.ServiceContracts.Mappers.MappingHelper.MappingsDefinieren.
(zie
\[source:trunk/Solution/Chiro.Gap.ServiceContracts.Mappers/MappingHelper.cs@630\#L17\].)
Als bron- en doeltype dezelfde veldnamen hebben, kan die configuratie zo
eenvoudig zijn als
<pre>\
Mapper.CreateMap<Bron,Doel>();\
</pre>
Maar omdat dat bij ons niet het geval is, is het allemaal wat
ingewikkelder. Mogelijk kunnen sommige zaken ook eenvoudiger dan hoe ze
in de MappingHelper gebeuren, maar ik gebruik [AutoMapper](AutoMapper.md) ook nog
niet zo lang ;-)
De method MappingHelper.!MappingsDefinieren moet nog wel ergens
opgeroepen worden, en voor ons project is dat in
\[source:trunk/Solution/Chiro.Gap.Services/Global.asax.cs@630\#L18\].
Meer documentatie over [AutoMapper](AutoMapper.md)
-----------------------------------------
... vind je op http://automapper.codeplex.com/
De backend van GAP
==================
De backend werd vernieuwd begin 2013. Een overzicht.
Concepten
---------
### Entiteiten
- POCO (wel dependency's HashSet en ICollection)
- geen exotische attributen
- niet meer automatisch gegenereerd
Voorbeeld: Land
<pre>\
public partial class Land\
{\
public Land()\
{\
this.BuitenLandsAdres = new HashSet<BuitenLandsAdres>();\
}
public int ID { get; set; }\
public string Naam { get; set; }
public virtual ICollection<BuitenLandsAdres> BuitenLandsAdres {
get; set; }\
}\
</pre>
### Repository's
- (vroegere DAO's)
- entity sets query'en via Linq
- volledig generiek
- lazy loading (voorlopig?)
- private members van de service
- geinstantieerd bij in constructor van service-implementatie
- delen context
Voorbeeld:
<pre>\
// Haal alle groepen op waarvoor ik gebruikersrecht heb\
var groepen = (from g in \_groepenRepo.Select()\
where g.GebruikersRecht.Any(gr => gr.Gav.Login == mijnLogin)\
select g).ToList();
</pre>
### Data context
- private member van de repository's: `_context`
- de context wordt gedeeld door alle repository's gedurende 1
service call. De dependency-injection-container zorgt hiervoor.
- voorziet link met Entity Framework
- (implementatie is overal al OK)
- SaveChanges van eender welke repository roept SaveChanges van
de (gedeelde) context op
Hier is iets dat niet helemaal klopt: De context is disposable. Een
repository ook. De dispose van een repository disposet de context, en
maakt dus alle andere repositories onbruikbaar. De repository's worden
nu allemaal samen gedisposed, en wel in de Dispose-method van de
services.
### Workers
- niet-triviale businesslogica
- doen geen expliciete data-access
- doen geen autorisatie (dat is nu voor de services)
- werken met objecten, en niet met ID's
### Data contracts
- ongewijzigd
- voor communicatie backend-frontend
- entiteiten worden op contracts gemapt
Entity Framework 4
------------------
Voor je de nieuwe backend kunt gebruiken, moet je [Entity Framework
4](http://www.microsoft.com/en-us/download/details.aspx?id=8363)
installeren.
Voorbeeldimplementaties
-----------------------
### Details van een groep ophalen: `GroepenService.DetailOphalen`
<pre>\
public GroepDetail DetailOphalen(int groepID)\
{\
var resultaat = new GroepDetail\
{\
Afdelingen = new List<AfdelingDetail>()\
};
var groepsWerkJaar =\
\_groepsWerkJarenRepo.Select()\
.Where(gwj => gwj.Groep.ID == groepID)\
.OrderByDescending(gwj => gwj.WerkJaar)\
.FirstOrDefault();
if (!\_autorisatieMgr.IsGav(groepsWerkJaar))\
{\
throw FaultExceptionHelper.GeenGav();\
}
Debug.Assert(groepsWerkJaar != null);
// Lazy loading zorgt ervoor dat alle zeken die nodig zijn\
// voor het mappen uit de database worden opgehaald.
Mapper.Map(groepsWerkJaar.Groep, resultaat);\
Mapper.Map(groepsWerkJaar.AfdelingsJaar, resultaat.Afdelingen);
return resultaat;\
}\
</pre>
### Naam van een groep wijzigen: `GroepenService.Bewaren`
<pre>\
public void Bewaren(GroepInfo groepInfo)\
{\
var groep = (from g in \_groepenRepo.Select()\
where g.ID == groepInfo.ID\
select g).FirstOrDefault();
// Momenteel ondersteunen we enkel het wijzigen van groepsnaam\
// en stamnummer. (En dat stamnummer wijzigen, mag dan nog enkel\
// als we super-gav zijn.)
if (!\_autorisatieMgr.IsGav(groep))\
{\
throw FaultExceptionHelper.GeenGav();\
}
Debug.Assert(groep != null);
if (String.Compare(groepInfo.StamNummer, groep.Code,
StringComparison.OrdinalIgnoreCase)
![]( 0 && )\_autorisatieMgr.IsSuperGav())\
{\
throw FaultExceptionHelper.GeenGav();\
}
groep.Naam = groepInfo.Naam;\
groep.Code = groepInfo.StamNummer;
// Er zijn wijzigingen die bewaard moeten worden.
\_groepenRepo.SaveChanges();\
}\
</pre>
### Nieuwe categorie toevoegen aan groep: `GroepenService.CategorieToevoegen`
<pre>\
public int CategorieToevoegen(int groepID, string naam, string code)\
{\
var groep = (from g in \_groepenRepo.Select()\
where g.ID == groepID\
select g).FirstOrDefault();
if (!\_autorisatieMgr.IsGav(groep))\
{\
throw FaultExceptionHelper.GeenGav();\
}
Debug.Assert(groep != null);
// Check of de categorie al bestaat\
// Merk op dat we de categorieen niet expliciet hebben\
// opgevraagd. Entity framework doet dat voor ons, met\
// lazy loading
var bestaande = (from c in groep.Categorie\
where String.Compare(c.Code, code, StringComparison.OrdinalIgnoreCase)
== 0\
select c).FirstOrDefault();
if (bestaande != null)\
{\
var info = Mapper.Map<Categorie, CategorieInfo>(bestaande);\
throw FaultExceptionHelper.BestaatAl(info);\
}
var nieuwe = new Categorie {Code = code, Naam = naam};\
groep.Categorie.Add(nieuwe);\
\_groepenRepo.SaveChanges();
return nieuwe.ID;\
}\
</pre>
Let op:
-------
### Data-access
- Met Linq op `_XXXrepo.Select()`
- Gegevens bewaren: `_XXXrepo.SaveChanges()`
### Gebruikersrechten controleren
- In service-implementatie (ipv workers)
- Via overloads van `_autorisatieMgr.IsGav(entiteit)`
- (ipv `isGavGroep(groepID)`, `isGavPersoon(persoonID)`,...)
- -> minder databasecalls
- minder foutgevoelig dan ID's
**OPGELET:** Er wordt gewerkt aan een nieuw systeem voor
gebruikersrechten.
- De functies `AutorisatieManager.IsGav` leveren `true` als de
entiteit gekoppeld is aan een groep waarop je GAV-permissies hebt.
- Op termijn zou ik dit willen vervangen door functies
- `AutorisatieManager.MagLezen(Persoon, Entity)`
- `AutorisatieManager.MagSchrijven(Persoon, Entity)`
- Zie ook [GebruikersRechten](GebruikersRechten.md).
### Exceptions
- `FaultExceptions` gegenereerd door `FaultExceptionHelper`.
- Bijv.: `throw new FaultExceptionHelper.GeenGav()`
- -> debugger kent stack trace
- -> code tamelijk leesbaar
De architectuur van GAP in grote lijnen
=======================================
Hieronder een oplijsting van de belangrijkste projecten uit de
GAP-solution (Voor een overzicht van alle projecten, zie
[NamespacesEnProjecten](NamespacesEnProjecten.md)).
Backend
-------
Zie [Backend](Backend.md).
Frontend
========
Chiro.Gap.WebApp
----------------
Een Asp.NET MVC2 frontend. De communicatie met de backend gebeurt via
service calls.
In het kort werkt het zo:
- Er wordt een http-request verstuurd dat er ongeveer zo
uitziet: gap-url/GroepID/ControllerNaam/ActieNaam/ID?parameter1=waarde1&parameter2=waarde2...
- De method ActieNaam van de klasse ControllerNaam
(in Chiro.Gap.WebApp/Controllers/ControllerNaam.cs) wordt uitgevoerd
met de gegeven parameters: ActieNaam(GroepID, ID,
waarde1, waarde2,...)
- Mogelijkheid 1: De method redirect naar een andere actie.
We zijn stilaan bezig om de app te migreren naar HTML5. Maar we zitten
nog in een experimentele fase. Het gebruik van Javascript moet nog wat
gestroomlijnd worden.
Branches
========
**OPMERKING:** Je kunt enkel pushen naar onze repository als je
schrijfrechten hebt. Heb je die niet, dan mag je ook een patch aan een
issue hangen, en de issue status op 'needs review' zetten.
**TODO:** Deze tekst bijwerken, meer ingespeeld op hoe het werkt voor
GAP. De GAP-workflow wordt wel min of meer uitgelegd in deze video over
git extensions: http://youtu.be/n-WbItytu0U
(Als je nog geen 'git-bash-venster' open hebt staan, klik dan in je
solution explorer rechts op een bestand, en kies 'git', 'git bash').
Lokale branches
---------------
Als je git gebruikt, dan is het de gewoonte dat je voor elke feature die
je implementeert, een lokale feature branch maakt. Om te kijken welke
branches je lokaal hebt, typ je in je bashvenster
<pre>\
git branch\
</pre>
De output ziet er dan ongeveer als volgt uit
<pre>\
eenbranch\
nogeenbranch
- dev\
</pre>
Het sterretje voor 'dev' geeft aan dat de dev-branch op dit moment is
uitgepakt.
Een nieuwe feature branch maak je door het volgende in te tikken:
<pre>\
git branch mijnfeaturebranch\
</pre>
Dit commando maakt een branch ('mijnfeaturebranch') vertrekkende van de
momenteel uitgepakte branch (in dit geval 'dev'). Om in die branch te
beginnen werken, moet je hem nog 'uitchecken':
<pre>\
git checkout mijnfeaturebranch\
</pre>
Alle bewerkingen die je nu [committen| commit](committen| commit.md), komen in de nieuwe
branch terecht.\
Interessant om weten: het maken van een nieuwe branch en het uitchecken
kan ook gecombineerd worden in één opdracht. Hiervoor geef je het
volgende in:
<pre>\
git checkout -b mijnfeaturebranch\
</pre>
Stel dat je nu een tijd in mijnfeaturebranch gewerkt hebt, misschien al
wel een paar commits gedaan, en er is plots een dringend probleem dat je
eerst moet aanpakken. Dan kun je erg gemakkelijk terug naar de dev
branch. Van daaruit maak je dan een nieuwe branch voor de dringende fix:
<pre>\
git checkout dev\
git checkout -b dringendefixbranch\
</pre>
(Eerst checken we de dev-branch opnieuw uit, zodat de nieuwe branch op
de dev-branch gebaseerd wordt. De constructie `checkout -b` combineert
het maken en uitchecken van de nieuwe branch.)
Je hebt nu terug de toestand zoals die was in de dev branch, en je kunt
de dringende fix beginnen implementeren. Je kan heen en weer switchen
tussen de verschillende branches met `git checkout`. (Let op:
ongecommitte wijzigingen worden meegenomen, maar niet gecommit, naar de
branch waar je naartoe switcht)
Stel dat je klaar bent met de wijzigingen in 'dringendefixbranch', dan
kun je de changes uit die branch makkelijk terug mergen naar de dev
branch:
<pre>\
git checkout dev\
git merge dringendefixbranch\
</pre>
Als dat allemaal goed is gelukt, kun je de 'dringendefixbranch' met een
gerust geweten opnieuw verwijderen:
<pre>\
git branch -d dringendefixbranch\
</pre>
(TODO: [ConflictResolution](ConflictResolution.md) documenteren.) Hierna kun je verder
werken in 'mijnfeaturebranch'. Optioneel kun je die branch 'opschuiven'
naar de kop van 'dev', zodat de fixes die je in dev gemerged hebt,
meteen ook in je feature branch opgenomen zijn: