JavaRush /Java блогу /Random-KY /Биз макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуп жатаб...
Roman Beekeeper
Деңгээл

Биз макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуп жатабыз. (1-бөлүк) - "Java долбоору Адан Яга чейин"

Группада жарыяланган
Салам! Бүгүн биз JavaRush макалалар тобуна жазылууну кошобуз. Бул GitHubдагы JRTB-5 чыгарууга туура келет. Мага түшүндүрүп берейин: JavaRush-та Макалалар деген бөлүм бар жана анда Макалалардын топтору бар. Идея - телеграмма боту аркылуу бир же бир нече топтордон жаңы макала жөнүндө эскертмелерди алуу."Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-1-бөлүк

JRTB-5 кошуу

Мен Ийгorк окуялары тобунун макалаларына кызыгам дейли . Ошондуктан, мен бул топтун жаңыртууларына жазылып, ар бир жолу жаңы басылмага шилтеме алгым келет. Бул тапшырманын бир бөлүгү катары, биз JavaRush топтор менен иштөө үчүн ачык API кантип колдонууну үйрөнүшүбүз керек. Дал ушул маалда ушундай нерсе келди. Бул жерде ачык API сүрөттөмөсүнө шилтеме .
Достор! Долбоордун жаңы codeу же жаңы макала чыкканда дароо билгиңиз келеби? Менин tg каналыма кошулунуз . Ал жерде мен өзүмдүн макалаларымды, ойлорумду жана ачык булактарды иштеп чыгууларды чогултам.

Swager деген эмне? Келгиле, аны азыр аныктайлы

Биз али шылуундук жөнүндө сүйлөшө элекпиз. Билбегендер үчүн мен кыскача түшүндүрүп берейин: бул serverдин API'син ачык карап, ага кандайдыр бир суроо-талаптарды жасоого аракет кыла турган жер. Адатта, свеггер мүмкүн болгон суроо-талаптарды топтойт. Биздин учурда үч топ бар: форум-суроо , топ , пост . Ар бир топто бул суроону түзүү үчүн бардык керектүү маалыматтарды көрсөткөн бир же бир нече суроо-талаптар болот (башкача айтканда, кандай кошумча параметрлерди өткөрүүгө болот, алар менен эмне кылуу керек, кандай http ыкмасы ж.б.у.с.). Мен сизге бул теманы көбүрөөк окуп, көрүүнү сунуштайм, анткени бул өнүгүүнүн дээрлик ар бириңизге туш боло турган бөлүгү. Аны түшүнүү үчүн, келгиле, JavaRushто канча топ бар экенин билели. Бул үчүн, контролер тобун кеңейтип, Сурам алуу /api/1.0/rest/groups/count тандаңыз . Бул бизге JavaRushтагы топтордун санын кайтарып берет. Келгиле, карап көрөлү: "Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-2-бөлүкСүрөт бул суроо бир нече параметрлерди (суроо, түрү, чыпка) колдой турганын көрсөтүп турат. Бул өтүнүчтү сынап көрүү үчүн, " Көрүп көрүңүз" баскычын табышыңыз керек , андан кийин бул параметрлерди конфигурациялоого болот: "Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-3-бөлүкСиз ошол жерден түрүн, чыпкасын жана суроону конфигурациялай аласыз (бул жерде чындыгында кызыктуу: бул тексттик издөө болот топ). Бирок азыр аны эч кандай чектөөсүз иштетип, JavaRushто канча топ бар экенин көрөлү. Бул үчүн, "Аткаруу" баскычын чыкылдатыңыз. Төмөндө бул суроого жооп болот (Сервердин жообу бөлүмүндө): Биз бардыгы болуп 30"Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-4-бөлүк топ бар экенин көрүп жатабыз , бул сурам 95ms ичинде аткарылган жана жоопто кээ бир баш тизмектер бар. Андан кийин, кээ бир параметрлерди конфигурациялоого аракет кылалы. КОМПАНИЯ маанисине барабар тип параметрин тандап алалы жана натыйжа кандай өзгөрөөрүн көрөлү: Алардын 4ү бар.Муну кантип текшерсе болот? Бул оңой: сиз веб-сайтка кирип, макала бөлүмүн таап, бардык топторду тандап, ал жерге тиешелүү чыпкаларды кошсоңуз болот ( https://javarush.com/groups/all?type=COMPANY ). Ооба, чындыгында, алардын 4 гана бар.Чындыгында үчөө бар болсо да :D Азырынча туура келет. Баса, университеттерди текшерсек, азырынча жок. Жөн эле көңүл ачуу үчүн, Javarush программасына кирип, кирбей турган браузерде чыпка = MY коюңуз, эмне болорун көрүңүз. Swager жөнүндө көбүрөөк - Habré боюнча бул макалада ."Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-5-бөлүк"Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-6-бөлүк

Топтор үчүн Javarush API үчүн кардар жазуу

Эми, ачык API негизинде биз суроо-талаптарды жасай турган, жооп ала турган жана кандай an objectтер келерин так билген Java кардарын жазабыз. Биз ошондой эле моделдер бөлүмүнөн (беттин ылдый жагында) an objectтерди алабыз . Келгиле, жаңы пакет түзүп, аны кызматтын, репозиторийдин жанында javarushclient деп атайлы. Келечекте биз муну Javarush Community уюмунун өзүнчө китепканасына көчүрөбүз жана аны көз карандылык катары гана колдонобуз. Биринчиден, JavaRush API'ге http сурамдарын түзүү үчүн Unitrest китепканасын кошушуңуз керек:
<dependency>
  <groupId>com.konghq</groupId>
  <artifactId>unirest-java</artifactId>
  <version>${unirest.version}</version>
</dependency>
Жана versionны касиеттер блогуна коюңуз:
<unirest.version>3.11.01</unirest.version>
Биз көз каранды болгондон кийин, биз codeду кошуп баштай алабыз. Келгиле, JavaRushGroupClient топтору үчүн кардарды жана JavaRushGroupClientImpl классында ишке ашырууну түзөлү. Бирок адегенде сиз DTO (маалыматтарды өткөрүп берүү an objectилери) түзүшүңүз керек - башкача айтканда, an objectтери кардар үчүн зарыл болгон бардык маалыматтарды алып жүрүүчү класстар. Бардык моделдерди свеггерден көрүүгө болот.Төмөндө Моделдер бөлүмү бар , анда сиз аларды санай аласыз. GroupDiscussionInfo свегерде ушундай көрүнөт: javarushclient пакетинде биз dto"Адан Яга Java долбоору": макалалардын тобуна жазылуу мүмкүнчүлүгүн кошуу.  1-7-бөлүк пакетин түзөбүз , ага swagger маалыматтарынын негизинде төмөнкү класстарды кошобуз:
  • 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
    }

  • Андан кийин - 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;
    }

GroupInfo жана GroupDiscussionInfo дээрлик бирдей болгондуктан , келгиле, аларды мураска байланыштыралы - 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;
}
Бизге GroupFilter сурамы үчүн да чыпка керек болот :
package com.github.javarushcommunity.jrtb.javarushclient.dto;

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

   UNKNOWN, MY, ALL
}
Идентификатор боюнча алуу өтүнүчүндө ал GroupDiscussionInfo кайтарып берет, ал эми топтордун жыйнагы үчүн сурамда GroupInfo менен GroupDiscussionInfoну ала аласыз. Сурамдардын түрү, суроосу, чыпкасы, офсети жана чеги болушу мүмкүн болгондуктан, келгиле, өзүнчө GroupRequestArgs классын түзүп , аны куруучу класс кылып алалы (куруучунун үлгүсү эмне экенин окуңуз):
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;
   }
}
Топтордун санын издөө үчүн ал бир аз башкача. Ал бир гана суроо, түрү жана чыпкасы бар. Жана сиз codeду кайталагыңыз келбейт окшойт. Ошол эле учурда аларды айкалыштыра баштаса, куруучулар менен иштегенде чиркин болуп чыгат. Ошентип, мен аларды бөлүп, codeду кайталоону чечтим. GroupCountRequestArgs ушундай көрүнөт :
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;
   }
}
Ооба, мен акыркы эки класста сурам түзүү үчүн картаны даярдай турган populateQueries ыкмасы бар экенин айткан жокмун (кийин көрөсүз). Жогоруда сүрөттөлгөн класстардын негизинде 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);
}
GroupInfo же GroupDiscussionInfo маалыматын алууну каалаган учурда эки башка суроо. Болбосо, бул сурамдар окшош жана бир гана айырмасы, биринде includeDiscussion желеги чын, экинчисинде ал жалган болот. Ошондуктан үч эмес, 4 ыкма болгон. Эми ишке ашырууну баштайлы:
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();
   }


}
Мен буга чейин тааныш Value annotationсын колдонуу менен конструктордогу API жолду кошом. Бул annotationнын ичиндеги маани касиеттер файлындагы талаага туура келерин билдирет. Ошондуктан, application.properties сайтына жаңы сап кошолу:
javarush.api.path=https://javarush.com/api/1.0/rest
Бул маани азыр бардык API кардарлары үчүн бир жерде болот жана API жолу өзгөрсө, биз аны тез жаңыртабыз. Мурда микроскоп менен мык кагып, Unirest аркылуу http суроосунан жооп алып, аны сапка которуп, анан бул жипти Джексон аркылуу талдап чыккам... Бул коркунучтуу, тажатма жана көптөгөн кошумча нерселерди талап кылды. Бул китепканадан анын кандай экенин көрө аласыз. Колума тийгенден кийин, мен баарын кайра карап чыгам.
Бул китепкананы жаңыртууну каалагандар - unirest китепканасынын куралдары аркылуу гана кабыл алуучу an objectтерди кошуу - жеке билдирүүгө же китепкананын өзүнө жаңы чыгарылыш катары жазыңыз. Бул сиз үчүн чыныгы иш тажрыйбасы болот, бирок мен каршы эмесмин. Мен толук codeду карап чыгып, керек болсо жардам берем.
Эми суроо туулат: биздин code биз күткөндөй иштейби? Жооп оңой: алар үчүн тест жазуу керек. Мен бир нече жолу айткандай, иштеп чыгуучулар тесттерди жаза бorши керек. Ошондуктан, Swagger UI колдонуу менен биз суроо-талаптарды жөнөтөбүз, жоопторду карап, аларды күтүлгөн натыйжа катары тесттерге алмаштырабыз. Топтордун саны статикалык эмес жана өзгөрүшү мүмкүн экенин дароо байкаган чыгарсыз. А сиз туура айтасыз. Жалгыз суроо - бул сан канчалык тез-тез өзгөрөт? Абдан сейрек, ошондуктан бир нече айдын ичинде биз бул маани статикалык болот деп айта алабыз. Ал эми бир нерсе өзгөрсө, биз тесттерди жаңыртабыз. Жолугушуу - 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());
   }
}
Тесттер мурдагыдай эле стилде жазылат. Ар бир суроо үчүн бир нече тесттер бар. Баарын сынап көрүүнүн эч кандай мааниси жок, анткени бул API буга чейин эң жакшы жол менен сыналган деп ойлойм.

Корутунду

Бул макаланын бир бөлүгү катары, биз JavaRush API'ге топтор үчүн Java кардарын коштук. Алар айткандай, жаша жана үйрөн. Мен бул кардарды жазып жатканда, мен алардын documentтеринен пайдаланып, алар берген an objectтер менен иштөөнү ыңгайлуу колдондум. Мен сунуш кылган тапшырмага көңүлүңүздү бурам. Ким кызыктырса, мага купуя кат жазыңыз, бул абдан кызыктуу тажрыйба болот деп ишенем. Бул биринчи бөлүгү болчу. Экинчиден, биз түздөн-түз кошуу буйругун ишке ашырабыз жана (эгерде биз аны бир макалага батырсак) колдонуучу жазылган топтордун тизмесин алуу менен кошобуз. Андан кийин, кимде ким ботко текст жазууга каалоосу жана таланты бар болсо, мага ПМ аркылуу жазыңыз. Мен бул маселе боюнча адис эмесмин жана кандайдыр бир жардам абдан пайдалуу болмок. Мунун баарын ачык codeдуу иштеп чыгуу катары формалдуу кылалы, кызыктуу болот! Адаттагыдай эле - лайк басыңыз, жазылыңыз, коңгуроо коюңуз , биздин долбоорго жылдыз бериңиз , комментарий жазыңыз жана макаланы баалаңыз!
пайдалуу шилтемелер

Сериядагы бардык материалдардын тизмеси ушул макаланын башында.

Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION