JavaRush /Java-Blog /Random-DE /Wir fügen die Möglichkeit hinzu, eine Gruppe von Artikeln...

Wir fügen die Möglichkeit hinzu, eine Gruppe von Artikeln zu abonnieren. (Teil 1) - „Java-Projekt von A bis Z“

Veröffentlicht in der Gruppe Random-DE
Hallo! Heute werden wir einer Artikelgruppe in JavaRush ein Abonnement hinzufügen. Dies entspricht der Ausgabe JRTB-5 auf GitHub. Lassen Sie es mich erklären: JavaRush hat einen Abschnitt namens Articles und darin gibt es Gruppen von Artikeln. Die Idee besteht darin, über einen Telegram-Bot Benachrichtigungen über einen neuen Artikel von einer oder mehreren Gruppen zu erhalten.„Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 1

JRTB-5 hinzufügen

Nehmen wir an, ich interessiere mich für Artikel aus der Gruppe „Erfolgsgeschichten“ . Daher möchte ich Updates dieser Gruppe abonnieren und jedes Mal einen Link zu einer neuen Veröffentlichung erhalten. Im Rahmen dieser Aufgabe müssen wir lernen, wie man die offene API für die Arbeit mit Gruppen in JavaRush verwendet. Genau in diesem Moment kam so etwas. Hier ist ein Link zu einer Beschreibung der offenen API .
Freunde! Möchten Sie sofort wissen, wann neuer Code für ein Projekt oder ein neuer Artikel veröffentlicht wird? Treten Sie meinem TG-Kanal bei . Dort sammle ich meine Artikel, Gedanken und Open-Source-Entwicklung zusammen.

Was ist Prahlerei? Lass es uns jetzt herausfinden

Über die Prahlerei haben wir noch nicht gesprochen. Für diejenigen, die es nicht wissen, erkläre ich es kurz: Dies ist ein Ort, an dem Sie die API eines Servers offen betrachten und versuchen können, einige Anfragen an ihn zu stellen. Typischerweise gruppiert ein Swagger mögliche Anfragen. In unserem Fall gibt es drei Gruppen: forum-question , group , post . In jeder Gruppe gibt es eine oder mehrere Anfragen, die alle zum Erstellen dieser Anfrage erforderlichen Daten angeben (d. h. welche zusätzlichen Parameter übergeben werden können, was mit ihnen geschehen soll, welche http-Methode usw.). Ich empfehle Ihnen, mehr zu diesem Thema zu lesen und anzuschauen, da dies der Teil der Entwicklung ist, dem fast jeder von Ihnen begegnen wird. Um das herauszufinden, wollen wir herausfinden, wie viele Gruppen es in JavaRush gibt. Erweitern Sie dazu die Gruppe „group-controller“ und wählen Sie „Get request /api/1.0/rest/groups/count “ aus . Es gibt uns die Anzahl der Gruppen in JavaRush zurück. Schauen wir mal: „Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 2Das Bild zeigt, dass diese Abfrage mehrere Parameter unterstützt (Abfrage, Typ, Filter). Um diese Anfrage auszuprobieren, müssen Sie die Schaltfläche „Ausprobieren“ finden . Anschließend können diese Parameter konfiguriert werden: „Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 3Sie können dort auch den Typ, den Filter und die Abfrage konfigurieren (das ist hier tatsächlich interessant: Dies wird eine Textsuche in einem sein Gruppe). Aber lassen Sie uns es zunächst ohne Einschränkungen ausführen und sehen, wie viele Gruppen es in JavaRush gibt. Klicken Sie dazu auf Ausführen. Direkt darunter wird eine Antwort (im Abschnitt „Serverantwort“) auf diese Anfrage angezeigt: „Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 4Wir sehen, dass es insgesamt 30 Gruppen gibt , diese Anfrage in 95 ms abgeschlossen wurde und die Antwort eine Reihe einiger Header enthält. Versuchen wir als Nächstes, einige Parameter zu konfigurieren. Wählen wir den Typparameter aus, der dem COMPANY-Wert entspricht, und sehen wir, wie sich das Ergebnis ändert: „Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 5Es gibt 4 davon. Wie kann ich das überprüfen? Es ist ganz einfach: Sie können auf die Website gehen, den Artikelbereich finden, alle Gruppen auswählen und dort den entsprechenden Filter hinzufügen ( https://javarush.com/groups/all?type=COMPANY ). Und ja, es gibt tatsächlich nur 4 davon. Obwohl es eigentlich drei sind :D „Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 6Soweit passt es. Wenn wir uns die Universitäten ansehen, gibt es übrigens noch keine. Sehen Sie sich zum Spaß an, was passiert, wenn Sie filter = MY in einem Browser festlegen, in dem Sie bei Javarush angemeldet und nicht angemeldet sind. Mehr zum Swagger – in diesem Artikel über Habré .

Schreiben eines Clients für die Javarush-API für Gruppen

Basierend auf der offenen API schreiben wir nun einen Java-Client, der Anfragen stellen und Antworten empfangen kann und genau weiß, welche Objekte ankommen. Wir werden auch Objekte aus dem Swagger aus dem Abschnitt „Modelle“ (ganz unten auf der Seite) übernehmen. Erstellen wir ein neues Paket und nennen es neben „service“ und „repository“ javarushclient. Zukünftig werden wir dies in eine separate Bibliothek innerhalb der Javarush-Community-Organisation verschieben und ausschließlich als Abhängigkeit verwenden. Zunächst müssen Sie Unitrest, eine Bibliothek zum Erstellen von http-Anfragen, zur JavaRush-API hinzufügen:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
Und fügen Sie die Version in den Eigenschaftenblock ein:
<unirest.version>3.11.01</unirest.version>
Sobald wir eine Abhängigkeit haben, können wir mit dem Hinzufügen von Code beginnen. Erstellen wir einen Client für JavaRushGroupClient-Gruppen und eine Implementierung in der JavaRushGroupClientImpl-Klasse. Zunächst müssen Sie jedoch DTOs (Data Transfer Objects) erstellen, also Klassen, deren Objekte alle für den Client erforderlichen Daten enthalten. Alle Models sind im Swagger zu sehen. Ganz unten gibt es einen Abschnitt Models , in dem man sie zählen kann. So sieht GroupDiscussionInfo im Swagger aus: Im javarushclient-Paket erstellen wir ein dto-„Java-Projekt von A bis Z“: Hinzufügen der Möglichkeit, eine Gruppe von Artikeln zu abonnieren.  Teil 1 - 7 Paket , zu dem wir basierend auf den Daten aus dem Swagger die folgenden Klassen hinzufügen:
  • MeGroupInfoStatus :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Member group status.
    */
    public enum MeGroupInfoStatus {
       UNKNOWN, CANDIDATE, INVITEE, MEMBER, EDITOR, MODERATOR, ADMINISTRATOR, BANNED
    }

  • MeGroupInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * Group information related to authorized user. If there is no user - will be null.
    */
    @Data
    public class MeGroupInfo {
       private MeGroupInfoStatus status;
       private Integer userGroupId;
    }

  • GroupInfoType :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Info type;
    */
    public enum GroupInfoType {
       UNKNOWN, CITY, COMPANY, COLLEGE, TECH, SPECIAL, COUNTRY
    }

  • UserDiscussionInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    
    /**
    * DTO for User discussion info.
    */
    @Data
    public class UserDiscussionInfo {
    
       private Boolean isBookmarked;
       private Integer lastTime;
       private Integer newCommentsCount;
    }

  • GroupVisibilitysdstatus :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    /**
    * Group Visibility status.
    */
    public enum GroupVisibilityStatus {
       UNKNOWN, RESTRICTED, PUBLIC, PROTECTED, PRIVATE, DISABLED, DELETED
    }

  • Dann - GroupInfo :

    package com.github.javarushcommunity.jrtb.javarushclient.dto;
    
    import lombok.Data;
    import lombok.ToString;
    
    /**
    * Group Info DTO class.
    */
    @Data
    @ToString
    public class GroupInfo {
    
       private Integer id;
       private String avatarUrl;
       private String createTime;
       private String description;
       private String key;
       private Integer levelToEditor;
       private MeGroupInfo meGroupInfo;
       private String pictureUrl;
       private String title;
       private GroupInfoType type;
       private Integer userCount;
       private GroupVisibilityStatus visibilityStatus;
    }

Da GroupInfo und GroupDiscussionInfo nahezu identisch sind, verknüpfen wir sie in der Vererbung – GroupDiscusionInfo :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

/**
* Group discussion info class.
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class GroupDiscussionInfo extends GroupInfo {

   private UserDiscussionInfo userDiscussionInfo;
   private Integer commentsCount;
}
Wir benötigen außerdem einen Filter für die GroupFilter- Anfrage :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

/**
* Filters for group requests.
*/
public enum GroupFilter {

   UNKNOWN, MY, ALL
}
Bei einer Anfrage zum Abrufen nach ID wird GroupDiscussionInfo zurückgegeben, und bei einer Anfrage nach einer Sammlung von Gruppen können Sie sowohl GroupInfo als auch GroupDiscussionInfo abrufen. Da Anfragen Typ, Abfrage, Filter, Offset und Limit haben können, erstellen wir eine separate GroupRequestArgs- Klasse und machen sie zu einer Builder-Klasse (lesen Sie, was das Builder-Muster ist):
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group requests.
*/
@Builder
@Getter
public class GroupRequestArgs {

   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   /**
    * specified where to start getting groups
    */
   private final Integer offset;
   /**
    * Limited number of groups.
    */
   private final Integer limit;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if(nonNull(query)) {
           queries.put("query", query);
       }
       if(nonNull(type)) {
           queries.put("type", type);
       }
       if(nonNull(filter)) {
           queries.put("filter", filter);
       }
       if(nonNull(offset)) {
           queries.put("offset", offset);
       }
       if(nonNull(limit)) {
           queries.put("limit", limit);
       }
       return queries;
   }
}
Bei der Suche nach der Anzahl der Gruppen ist es etwas anders. Es gibt nur Abfrage, Typ und Filter. Und es scheint, dass Sie den Code nicht duplizieren möchten. Wenn Sie jedoch anfangen, sie zu kombinieren, wird es bei der Arbeit mit Bauherren hässlich. Also beschloss ich, sie zu trennen und den Code zu wiederholen. So sieht GroupCountRequestArgs aus :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

import lombok.Builder;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

import static java.util.Objects.nonNull;

/**
* Request arguments for group count requests.
*/
@Builder
@Getter
public class GroupsCountRequestArgs {
   private final String query;
   private final GroupInfoType type;
   private final GroupFilter filter;

   public Map populateQueries() {
       Map queries = new HashMap<>();
       if (nonNull(query)) {
           queries.put("query", query);
       }
       if (nonNull(type)) {
           queries.put("type", type);
       }
       if (nonNull(filter)) {
           queries.put("filter", filter);
       }
       return queries;
   }
}
Ja, ich habe nicht erwähnt, dass die letzten beiden Klassen über eine Methode „populateQueries“ verfügen, die die Karte für die Erstellung einer Abfrage vorbereitet (Sie werden sie später sehen). Basierend auf den oben beschriebenen Klassen erstellen wir eine Schnittstelle für JavaRushGroupClient :
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;

import java.util.List;

/**
 * Client for Javarush Open API corresponds to Groups.
 */
public interface JavaRushGroupClient {

    /**
     * Get all the {@link GroupInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}.
     * @return the collection of the {@link GroupInfo} objects.
     */
    List<GroupInfo> getGroupList(GroupRequestArgs requestArgs);

    /**
     * Get all the {@link GroupDiscussionInfo} filtered by provided {@link GroupRequestArgs}.
     *
     * @param requestArgs provided {@link GroupRequestArgs}
     * @return the collection of the {@link GroupDiscussionInfo} objects.
     */
    List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs);

    /**
     * Get count of groups filtered by provided {@link GroupRequestArgs}.
     *
     * @param countRequestArgs provided {@link GroupsCountRequestArgs}.
     * @return the count of the groups.
     */
    Integer getGroupCount(GroupsCountRequestArgs countRequestArgs);

    /**
     * Get {@link GroupDiscussionInfo} by provided ID.
     *
     * @param id provided ID.
     * @return {@link GroupDiscussionInfo} object.
     */
    GroupDiscussionInfo getGroupById(Integer id);
}
Zwei verschiedene Anfragen für den Fall, dass wir GroupInfo- oder GroupDiscussionInfo-Informationen hinzufügen möchten. Ansonsten sind diese Anfragen identisch und der einzige Unterschied besteht darin, dass in der einen das Flag „includeDiscussion“ wahr und in der anderen falsch ist. Daher gab es vier Methoden, nicht drei. Beginnen wir nun mit der Implementierung:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import kong.unirest.GenericType;
import kong.unirest.Unirest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;

/**
* Implementation of the {@link JavaRushGroupClient} interface.
*/
@Component
public class JavaRushGroupClientImpl implements JavaRushGroupClient {

   private final String javarushApiGroupPath;

   public JavaRushGroupClientImpl(@Value("${javarush.api.path}") String javarushApi) {
       this.javarushApiGroupPath = javarushApi + "/groups";
   }

   @Override
   public List<GroupInfo> getGroupList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupInfo>>() {
               })
               .getBody();
   }

   @Override
   public List<GroupDiscussionInfo> getGroupDiscussionList(GroupRequestArgs requestArgs) {
       return Unirest.get(javarushApiGroupPath)
               .queryString(requestArgs.populateQueries())
               .asObject(new GenericType<list<GroupDiscussionInfo>>() {
               })
               .getBody();
   }

   @Override
   public Integer getGroupCount(GroupsCountRequestArgs countRequestArgs) {
       return Integer.valueOf(
               Unirest.get(String.format("%s/count", javarushApiGroupPath))
                       .queryString(countRequestArgs.populateQueries())
                       .asString()
                       .getBody()
       );
   }

   @Override
   public GroupDiscussionInfo getGroupById(Integer id) {
       return Unirest.get(String.format("%s/group%s", javarushApiGroupPath, id.toString()))
               .asObject(GroupDiscussionInfo.class)
               .getBody();
   }


}
Den Pfad zur API füge ich im Konstruktor hinzu, indem ich die bereits bekannte Value-Annotation verwende. Dies impliziert, dass der Wert innerhalb der Anmerkung einem Feld in der Eigenschaftendatei entspricht. Deshalb fügen wir application.properties eine neue Zeile hinzu:
javarush.api.path=https://javarush.com/api/1.0/rest
Dieser Wert befindet sich nun für alle API-Clients an einem Ort. Wenn sich der API-Pfad ändert, werden wir ihn schnell aktualisieren. Zuvor habe ich mit einem Mikroskop Nägel eingeschlagen, über Unirest eine Antwort auf eine http-Anfrage erhalten, diese in einen String übersetzt und diesen String dann über Jackson analysiert ... Es war beängstigend, mühsam und erforderte viele zusätzliche Dinge. In dieser Bibliothek können Sie sehen, wie es aussieht. Sobald ich es in die Hände bekomme, werde ich alles umgestalten.
Jeder, der versuchen möchte, diese Bibliothek zu aktualisieren – Empfangsobjekte nur mit den Tools der Unirest-Bibliothek hinzuzufügen – schreibt in einer persönlichen Nachricht oder als neue Ausgabe in der Bibliothek selbst. Das wird für Sie eine echte Berufserfahrung sein, aber das macht mir nichts aus. Ich werde eine vollständige Codeüberprüfung durchführen und bei Bedarf helfen.
Die Frage ist nun: Funktioniert unser Code wie erwartet? Die Antwort ist einfach: Sie müssen nur Tests für sie schreiben. Wie ich bereits mehrfach gesagt habe, müssen Entwickler in der Lage sein, Tests zu schreiben. Daher senden wir mithilfe unserer Swagger-Benutzeroberfläche Anfragen, sehen uns die Antworten an und ersetzen sie als erwartetes Ergebnis in den Tests. Möglicherweise ist Ihnen sofort aufgefallen, dass die Anzahl der Gruppen nicht statisch ist und sich ändern kann. Und du hast recht. Die Frage ist nur, wie oft ändert sich diese Zahl? Sehr selten, daher können wir über mehrere Monate hinweg sagen, dass dieser Wert statisch sein wird. Und wenn sich etwas ändert, werden wir die Tests aktualisieren. Treffen Sie - JavaRushGroupClientTest:
package com.github.javarushcommunity.jrtb.javarushclient;

import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupDiscussionInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfo;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupRequestArgs;
import com.github.javarushcommunity.jrtb.javarushclient.dto.GroupsCountRequestArgs;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static com.github.javarushcommunity.jrtb.javarushclient.dto.GroupInfoType.TECH;

@DisplayName("Integration-level testing for JavaRushGroupClientImplTest")
class JavaRushGroupClientTest {

   private final JavaRushGroupClient groupClient = new JavaRushGroupClientImpl("https://javarush.com/api/1.0/rest");

   @Test
   public void shouldProperlyGetGroupsWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupInfo> groupList = groupClient.getGroupList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupsDiscWithEmptyArgs() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder().build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertFalse(groupList.isEmpty());
   }

   @Test
   public void shouldProperlyGetGroupDiscWithOffSetAndLimit() {
       //given
       GroupRequestArgs args = GroupRequestArgs.builder()
               .offset(1)
               .limit(3)
               .build();

       //when
       List<GroupDiscussionInfo> groupList = groupClient.getGroupDiscussionList(args);

       //then
       Assertions.assertNotNull(groupList);
       Assertions.assertEquals(3, groupList.size());
   }

   @Test
   public void shouldProperlyGetGroupCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder().build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(30, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupTECHCount() {
       //given
       GroupsCountRequestArgs args = GroupsCountRequestArgs.builder()
               .type(TECH)
               .build();

       //when
       Integer groupCount = groupClient.getGroupCount(args);

       //then
       Assertions.assertEquals(7, groupCount);
   }

   @Test
   public void shouldProperlyGetGroupById() {
       //given
       Integer androidGroupId = 16;

       //when
       GroupDiscussionInfo groupById = groupClient.getGroupById(androidGroupId);

       //then
       Assertions.assertNotNull(groupById);
       Assertions.assertEquals(16, groupById.getId());
       Assertions.assertEquals(TECH, groupById.getType());
       Assertions.assertEquals("android", groupById.getKey());
   }
}
Die Tests sind im gleichen Stil wie zuvor geschrieben. Für jede Anfrage gibt es mehrere Tests. Es macht keinen Sinn, alles zu testen, da ich denke, dass diese API bereits bestens getestet wurde.

Abschluss

Im Rahmen dieses Artikels haben wir der JavaRush-API einen Java-Client für Gruppen hinzugefügt. Wie sie sagen: Lebe und lerne. Während ich diesen Client schrieb, habe ich mir die Dokumentation zunutze gemacht und die Arbeit mit den von ihm bereitgestellten Objekten bequem genutzt. Ich mache Sie auf die von mir vorgeschlagene Aufgabe aufmerksam. Wenn jemand Interesse hat, schreibt mir eine private Nachricht, ich bin mehr als sicher, dass es eine sehr interessante Erfahrung wird. Das war der erste Teil. Im zweiten Schritt werden wir den Befehl zum Hinzufügen direkt implementieren und (wenn wir ihn in einen Artikel einfügen) das Abrufen einer Liste der Gruppen hinzufügen, bei denen der Benutzer abonniert ist. Als nächstes schreibt mir bitte jeder, der Lust und Talent hat, Texte für den Bot zu schreiben, per PN. Ich bin kein Experte in dieser Angelegenheit und jede Hilfe wäre sehr hilfreich. Lassen Sie uns das alles als Open-Source-Entwicklung formalisieren, es wird interessant sein! Nun, wie immer – liken, abonnieren, klingeln , unserem Projekt einen Stern geben , Kommentare schreiben und den Artikel bewerten!
Nützliche Links

Eine Liste aller Materialien der Serie finden Sie am Anfang dieses Artikels.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION