diff --git a/.gitignore b/.gitignore
index a1c2a23..726fc30 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+.idea/
+out/
diff --git a/README.md b/README.md
index 407a72f..504af82 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-# intellij-gitea-plugin
\ No newline at end of file
+# Gitea issue tracker integration plugin (http://gitea.io) for Jetbrains IDE platform.
\ No newline at end of file
diff --git a/gitea_plugin.iml b/gitea_plugin.iml
new file mode 100644
index 0000000..e025b20
--- /dev/null
+++ b/gitea_plugin.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
new file mode 100644
index 0000000..836df08
--- /dev/null
+++ b/resources/META-INF/plugin.xml
@@ -0,0 +1,28 @@
+
+ biz.elfuego.idea.issues.gitea
+ Gitea issues plugin
+ 1.0
+ elfuego.biz
+
+
+
+
+ ]]>
+
+
+
+
+ com.intellij.modules.lang
+ com.intellij.tasks
+
+
+
+
+
+
+
+
+
diff --git a/resources/resources/gitea.png b/resources/resources/gitea.png
new file mode 100644
index 0000000..a4080c1
Binary files /dev/null and b/resources/resources/gitea.png differ
diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java
new file mode 100644
index 0000000..8edfc59
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepository.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea;
+
+import biz.elfuego.idea.issues.gitea.model.GiteaProject;
+import biz.elfuego.idea.issues.gitea.model.GiteaTask;
+import biz.elfuego.idea.issues.gitea.util.Consts;
+import biz.elfuego.idea.issues.gitea.util.Consts.CommentFields;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.tasks.Comment;
+import com.intellij.tasks.CustomTaskState;
+import com.intellij.tasks.Task;
+import com.intellij.tasks.impl.BaseRepositoryImpl;
+import com.intellij.tasks.impl.SimpleComment;
+import com.intellij.util.xmlb.annotations.AbstractCollection;
+import com.intellij.util.xmlb.annotations.Tag;
+import com.intellij.util.xmlb.annotations.Transient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.auth.AuthPolicy;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.StringRequestEntity;
+import org.apache.commons.lang.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.InputStreamReader;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static biz.elfuego.idea.issues.gitea.util.Utils.*;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+@Tag("Gitea")
+class GiteaRepository extends BaseRepositoryImpl {
+ private String userId = null;
+ private List projects = new ArrayList<>();
+ private GiteaProject selectedProject = null;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public GiteaRepository() {
+ super();
+ }
+
+ @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+ public GiteaRepository(GiteaRepositoryType type) {
+ super(type);
+ setUseHttpAuthentication(true);
+ setUrl(Consts.Url.DEFAULT);
+ }
+
+ @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+ public GiteaRepository(GiteaRepository other) {
+ super(other);
+ userId = other.userId;
+ projects = other.projects;
+ selectedProject = other.selectedProject;
+ }
+
+ @Nullable
+ @Override
+ public Task findTask(@NotNull String s) throws Exception {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public Task[] getIssues(@Nullable String query, int offset, int limit, boolean withClosed, @NotNull ProgressIndicator cancelled) throws Exception {
+ return getIssues();
+ }
+
+ @NotNull
+ @Override
+ @SuppressWarnings("CloneDoesntCallSuperClone")
+ public GiteaRepository clone() {
+ return new GiteaRepository(this);
+ }
+
+ @Nullable
+ @Override
+ public CancellableConnection createCancellableConnection() {
+ return new CancellableConnection() {
+ @Override
+ protected void doTest() throws Exception {
+ GiteaRepository.this.doTest();
+ }
+
+ @Override
+ public void cancel() {
+ //Jetbrains left this method blank in their generic task repo as well. Just let it time out?
+ }
+ };
+ }
+
+ @Nullable
+ @Override
+ public String extractId(@NotNull String taskName) {
+ Matcher matcher = Pattern.compile("(d+)").matcher(taskName);
+ return matcher.find() ? matcher.group(1) : null;
+ }
+
+ @NotNull
+ @Override
+ public Set getAvailableTaskStates(@NotNull Task task) {
+ Set result = new HashSet<>();
+ for (Consts.States state : Consts.States.values()) {
+ String name = state.name().toLowerCase();
+ result.add(new CustomTaskState(name, name));
+ }
+ return result;
+ }
+
+ @Override
+ public void setTaskState(@NotNull Task task, @NotNull CustomTaskState state) throws Exception {
+ GiteaTaskImpl giteaTask = null;
+ if (task instanceof GiteaTaskImpl) {
+ giteaTask = (GiteaTaskImpl) task;
+ } else {
+ Task[] tasks = getIssues();
+ for (Task t : tasks) {
+ if (task.getId().equals(t.getId())) {
+ giteaTask = (GiteaTaskImpl) t;
+ }
+ }
+ }
+
+ if (giteaTask == null) {
+ throw new Exception("Task not found");
+ }
+
+ giteaTask.task.setState(state.getId());
+ JsonObject jsonData = new JsonObject();
+ jsonData.addProperty("state", state.getId());
+ StringRequestEntity data = new StringRequestEntity(
+ jsonData.toString(),
+ "application/json",
+ "UTF-8"
+ );
+ HttpMethod patchTask = getPatchMethod(getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName()
+ + Consts.EndPoint.ISSUES + "/" + giteaTask.getId(), data);
+ executeMethod(patchTask);
+ }
+
+ @Override
+ protected int getFeatures() {
+ return NATIVE_SEARCH | STATE_UPDATING;
+ }
+
+ private void doTest() throws Exception {
+ userId = null;
+ checkSetup();
+ JsonElement response = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.ME));
+ final JsonObject obj = getObject(response);
+ if (obj.has("id") && obj.has("login"))
+ return;
+ throw new Exception(Consts.ERROR);
+ }
+
+ @NotNull
+ private String getApiUrl() {
+ return getUrl() + Consts.EndPoint.API;
+ }
+
+ @Override
+ public boolean isConfigured() {
+ boolean result = true;
+ if (!super.isConfigured()) {
+ result = false;
+ }
+ if (result && StringUtil.isEmpty(this.getUrl())) {
+ result = false;
+ }
+ if (result && StringUtil.isEmpty(this.getUsername())) {
+ result = false;
+ }
+ if (result && StringUtil.isEmpty(this.getPassword())) {
+ result = false;
+ }
+ return result;
+ }
+
+ private void checkSetup() throws Exception {
+ String result = "";
+ int errors = 0;
+ if (StringUtil.isEmpty(getUrl())) {
+ result += "Server";
+ errors++;
+ }
+ if (StringUtil.isEmpty(getUsername())) {
+ result += !StringUtils.isEmpty(result) ? " & " : "";
+ result += "Username";
+ errors++;
+ }
+ if (StringUtil.isEmpty(getPassword())) {
+ result += !StringUtils.isEmpty(result) ? " & " : "";
+ result += "Password";
+ errors++;
+ }
+ if (!result.isEmpty()) {
+ throw new Exception(result + ((errors > 1) ? " are required" : " is required"));
+ }
+ }
+
+ private Task[] getIssues() throws Exception {
+ if (ifNoSelectedProj()) return new Task[]{};
+ ensureUserId();
+ List result = new ArrayList<>();
+
+ final String url = getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName() + Consts.EndPoint.ISSUES;
+ final JsonElement response = executeMethod(new GetMethod(url));
+ JsonArray tasks = getArray(response);
+ for (int i = 0; i < tasks.size(); i++) {
+ JsonObject current = tasks.get(i).getAsJsonObject();
+ GiteaTask raw = new GiteaTask(selectedProject, current);
+ if (!raw.isValid()) {
+ continue;
+ }
+ GiteaTaskImpl mapped = new GiteaTaskImpl(this, raw);
+ result.add(mapped);
+ }
+ Collections.sort(result);
+ Task[] primArray = new Task[result.size()];
+ return result.toArray(primArray);
+ }
+
+ private boolean ifNoSelectedProj() {
+ return selectedProject == null || selectedProject.getId().equals("-1");
+ }
+
+ public Comment[] getComments(GiteaTaskImpl task) throws Exception {
+ if (ifNoSelectedProj()) return new Comment[]{};
+ ensureUserId();
+ List result = new ArrayList<>();
+
+ final String url = getApiUrl() + Consts.EndPoint.REPOS + selectedProject.getName() + Consts.EndPoint.ISSUES
+ + "/" + task.getId() + Consts.EndPoint.COMMENTS;
+ final JsonElement response = executeMethod(new GetMethod(url));
+ JsonArray comments = getArray(response);
+ for (int i = 0; i < comments.size(); i++) {
+ JsonObject current = comments.get(i).getAsJsonObject();
+ Date date = getDate(current, CommentFields.DATE);
+ String text = getString(current, CommentFields.TEXT, "");
+ JsonObject user = getObject(current, CommentFields.USER);
+ String author = getString(user, CommentFields.FULLNAME, "");
+ if (author.isEmpty())
+ author = getString(user, CommentFields.USERNAME, "");
+ result.add(new SimpleComment(date, author, text));
+ }
+ Comment[] primArray = new Comment[result.size()];
+ return result.toArray(primArray);
+ }
+
+ private JsonElement executeMethod(@NotNull HttpMethod method) throws Exception {
+ method.addRequestHeader("Content-type", "application/json");
+ List authPrefs = Collections.singletonList(AuthPolicy.BASIC);
+ method.getParams().setParameter(AuthPolicy.AUTH_SCHEME_PRIORITY, authPrefs);
+ getHttpClient().executeMethod(method);
+
+ if (method.getStatusCode() != HttpStatus.SC_OK && method.getStatusCode() != HttpStatus.SC_CREATED) {
+ throw new Exception("Request failed with HTTP error: " + method.getStatusText());
+ }
+
+ return new JsonParser().parse(new InputStreamReader(method.getResponseBodyAsStream(), "UTF-8"));
+ }
+
+ private HttpMethod getPatchMethod(String url, StringRequestEntity data) {
+ PostMethod patchMethod = new PostMethod(url) {
+ @Override
+ public String getName() {
+ return "PATCH";
+ }
+ };
+ patchMethod.setRequestEntity(data);
+ return patchMethod;
+ }
+
+ private void ensureUserId() throws Exception {
+ if (userId == null || userId.isEmpty()) {
+ JsonElement result = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.ME));
+ userId = result.getAsJsonObject().get("login").getAsJsonPrimitive().getAsString();
+ }
+ }
+
+ @Transient
+ List getProjectList() throws Exception {
+ ensureUserId();
+ if (projects == null || projects.isEmpty()) {
+ JsonElement response = executeMethod(new GetMethod(getApiUrl() + Consts.EndPoint.REPOS_SEARCH + userId));
+ JsonArray query = getOkData(response);
+ List result = new ArrayList<>();
+ for (int i = 0; i < query.size(); i++) {
+ JsonObject current = getObject(query.get(i));
+ GiteaProject project = new GiteaProject().setId(getString(current, "id", ""))
+ .setName(getString(current, "full_name", ""));
+ if (!project.isValid()) {
+ continue;
+ }
+ result.add(project);
+ }
+ projects = result;
+ }
+ return projects;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public GiteaProject getSelectedProject() {
+ return selectedProject;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setSelectedProject(GiteaProject selectedProject) {
+ this.selectedProject = selectedProject;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public String getUserId() {
+ return userId;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ @AbstractCollection(surroundWithTag = false, elementTag = "GiteaProject", elementTypes = GiteaProject.class)
+ public List getProjects() {
+ return projects;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setProjects(List projects) {
+ this.projects = projects;
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java
new file mode 100644
index 0000000..dfcbd35
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryEditor.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea;
+
+import biz.elfuego.idea.issues.gitea.model.GiteaProject;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.tasks.config.BaseRepositoryEditor;
+import com.intellij.tasks.impl.TaskUiUtil;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.Consumer;
+import com.intellij.util.ui.FormBuilder;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.List;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class GiteaRepositoryEditor extends BaseRepositoryEditor {
+ private JBLabel projectLabel;
+ private ComboBox projectBox;
+
+ GiteaRepositoryEditor(GiteaRepository repository, Project project, Consumer consumer) {
+ super(project, repository, consumer);
+
+ installListener(projectBox);
+
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ initialize();
+ }
+ });
+ }
+
+ private void initialize() {
+ if (myRepository.isConfigured()) {
+ new FetchProjectsTask().queue();
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCustomPanel() {
+ projectBox = new ComboBox(300);
+ projectBox.setRenderer(new TaskUiUtil.SimpleComboBoxRenderer("Set URL, username, and password"));
+ projectLabel = new JBLabel("Project:", SwingConstants.RIGHT);
+ projectLabel.setLabelFor(projectBox);
+
+ return new FormBuilder().setAlignLabelOnRight(true)
+ .addLabeledComponent(projectLabel, projectBox)
+ .getPanel();
+ }
+
+ @Override
+ public void setAnchor(@Nullable JComponent anchor) {
+ super.setAnchor(anchor);
+ projectLabel.setAnchor(anchor);
+ }
+
+ @Override
+ protected void afterTestConnection(boolean connectionSuccessful) {
+ if (connectionSuccessful) {
+ new FetchProjectsTask().queue();
+ }
+ }
+
+ @Override
+ public void apply() {
+ super.apply();
+ myRepository.setSelectedProject((GiteaProject) projectBox.getSelectedItem());
+ myTestButton.setEnabled(myRepository.isConfigured());
+ }
+
+ private class FetchProjectsTask extends TaskUiUtil.ComboBoxUpdater {
+ private FetchProjectsTask() {
+ super(GiteaRepositoryEditor.this.myProject, "Downloading Gitea projects...", projectBox);
+ }
+
+ @Override
+ public GiteaProject getExtraItem() {
+ return GiteaProject.UNSPECIFIED_PROJECT;
+ }
+
+ @Nullable
+ @Override
+ public GiteaProject getSelectedItem() {
+ return myRepository.getSelectedProject();
+ }
+
+ @NotNull
+ @Override
+ protected List fetch(@NotNull ProgressIndicator indicator) throws Exception {
+ return myRepository.getProjectList();
+ }
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java
new file mode 100644
index 0000000..7479661
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/GiteaRepositoryType.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.IconLoader;
+import com.intellij.tasks.TaskRepository;
+import com.intellij.tasks.config.TaskRepositoryEditor;
+import com.intellij.tasks.impl.BaseRepositoryType;
+import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class GiteaRepositoryType extends BaseRepositoryType {
+ @NotNull
+ @Override
+ public String getName() {
+ return "Gitea";
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return IconLoader.getIcon("/resources/gitea.png");
+ }
+
+ @NotNull
+ @Override
+ public TaskRepositoryEditor createEditor(GiteaRepository repository, Project project, Consumer consumer) {
+ return new GiteaRepositoryEditor(repository, project, consumer);
+ }
+
+ @NotNull
+ @Override
+ public TaskRepository createRepository() {
+ return new GiteaRepository(this);
+ }
+
+ @Override
+ public Class getRepositoryClass() {
+ return GiteaRepository.class;
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java b/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java
new file mode 100644
index 0000000..5118a19
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/GiteaTaskImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea;
+
+import biz.elfuego.idea.issues.gitea.model.GiteaProject;
+import biz.elfuego.idea.issues.gitea.model.GiteaTask;
+import biz.elfuego.idea.issues.gitea.util.Consts;
+import com.intellij.openapi.util.IconLoader;
+import com.intellij.tasks.Comment;
+import com.intellij.tasks.Task;
+import com.intellij.tasks.TaskRepository;
+import com.intellij.tasks.TaskType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.Date;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class GiteaTaskImpl extends Task implements Comparable {
+ private GiteaProject project;
+ private GiteaRepository repository;
+ private Comment[] comments;
+ GiteaTask task;
+
+ GiteaTaskImpl(@NotNull GiteaRepository repository, @NotNull GiteaTask task) {
+ this.repository = repository;
+ this.task = task;
+ this.project = task.getProject();
+ }
+
+ @NotNull
+ @Override
+ public String getId() {
+ return task.getId();
+ }
+
+ @NotNull
+ @Override
+ public String getSummary() {
+ return task.getTitle();
+ }
+
+ @Nullable
+ @Override
+ public String getDescription() {
+ return task.getDescription();
+ }
+
+ @NotNull
+ @Override
+ public Comment[] getComments() {
+ if (comments == null) {
+ try {
+ comments = repository.getComments(this);
+ } catch(Exception ignored) {}
+ if (comments == null)
+ comments = new Comment[0];
+ }
+ return comments;
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return IconLoader.getIcon("/resources/gitea.png");
+ }
+
+ @NotNull
+ @Override
+ public TaskType getType() {
+ return TaskType.OTHER;
+ }
+
+ @Nullable
+ @Override
+ public Date getUpdated() {
+ return task.getUpdatedAt();
+ }
+
+ @Nullable
+ @Override
+ public Date getCreated() {
+ return task.getCreatedAt();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return Consts.States.CLOSED.name().toLowerCase().equals(task.getState());
+ }
+
+ @Override
+ public boolean isIssue() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public TaskRepository getRepository() {
+ return repository;
+ }
+
+ @Nullable
+ @Override
+ public String getIssueUrl() {
+ return repository.getUrl() + "/" + project.getName() + Consts.EndPoint.ISSUES + "/" + task.getId();
+ }
+
+ @Override
+ public int compareTo(@NotNull GiteaTaskImpl o) {
+ int me = Integer.parseInt(this.task.getId());
+ int them = Integer.parseInt(o.task.getId());
+
+ return Integer.compare(me, them);
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java b/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java
new file mode 100644
index 0000000..801606d
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/model/GiteaProject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea.model;
+
+import biz.elfuego.idea.issues.gitea.util.Consts;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class GiteaProject {
+ private String id;
+ private String name;
+
+ public String getId() {
+ return id;
+ }
+
+ public GiteaProject setId(String mProjectId) {
+ this.id = mProjectId;
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public GiteaProject setName(String mProjectTitle) {
+ this.name = mProjectTitle;
+ return this;
+ }
+
+ public boolean isValid() {
+ return !(id.equals("") || name.equals(""));
+ }
+
+ @Override
+ public String toString() {
+ return name != null ? name : super.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ GiteaProject that = (GiteaProject) o;
+
+ return (id != null ? id.equals(that.id) : that.id == null) && (name != null ? name.equals(that.name) : that.name == null);
+ }
+
+ public static final GiteaProject UNSPECIFIED_PROJECT = new GiteaProject() {
+ @Override
+ public String getName() {
+ return "-- Select A Project (Required) --";
+ }
+
+ @Override
+ public String getId() {
+ return Consts.UNSPEC_PROJ_ID;
+ }
+
+ @Override
+ public String toString() {
+ return getName();
+ }
+ };
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java b/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java
new file mode 100644
index 0000000..1de86d1
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/model/GiteaTask.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea.model;
+
+import biz.elfuego.idea.issues.gitea.util.Consts.TaskFields;
+import com.google.gson.JsonObject;
+import org.apache.http.util.TextUtils;
+
+import java.util.Date;
+
+import static biz.elfuego.idea.issues.gitea.util.Utils.getDate;
+import static biz.elfuego.idea.issues.gitea.util.Utils.getString;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class GiteaTask {
+ private GiteaProject project;
+ private String id;
+ private String title;
+ private String description;
+ private Date createdAt;
+ private Date updatedAt;
+ private String state;
+ private String assignee;
+
+ public GiteaTask(GiteaProject project, JsonObject json) {
+ this.project = project;
+ this.fromJson(json);
+ }
+
+ public GiteaProject getProject() {
+ return project;
+ }
+
+ public void setProject(GiteaProject project) {
+ this.project = project;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Date getCreatedAt() {
+ return createdAt;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setCreatedAt(Date createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public Date getUpdatedAt() {
+ return updatedAt;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setUpdatedAt(Date updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getAssignee() {
+ return assignee;
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ public void setAssignee(String assignee) {
+ this.assignee = assignee;
+ }
+
+ public boolean isValid() {
+ return !(TextUtils.isEmpty(id) ||
+ TextUtils.isEmpty(title) ||
+ (createdAt == null) ||
+ (updatedAt == null) ||
+ TextUtils.isEmpty(state));
+ }
+
+ private void fromJson(JsonObject current) {
+ if (current.has(TaskFields.ID)) {
+ this.setId(getString(current, TaskFields.ID, ""));
+ }
+ if (current.has(TaskFields.TITLE)) {
+ this.setTitle(getString(current, TaskFields.TITLE, ""));
+ }
+ if (current.has(TaskFields.DESCRIPTION)) {
+ this.setDescription(getString(current, TaskFields.DESCRIPTION, ""));
+ }
+ if (current.has(TaskFields.CREATEDAT)) {
+ this.setCreatedAt(getDate(current, TaskFields.CREATEDAT));
+ }
+ if (current.has(TaskFields.UPDATEDAT)) {
+ this.setUpdatedAt(getDate(current, TaskFields.UPDATEDAT));
+ }
+ if (current.has(TaskFields.STATE)) {
+ this.setState(getString(current, TaskFields.STATE, ""));
+ }
+ if (current.has(TaskFields.ASSIGNEE)) {
+ this.setAssignee(getString(current, TaskFields.ASSIGNEE, ""));
+ }
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/util/Consts.java b/src/biz/elfuego/idea/issues/gitea/util/Consts.java
new file mode 100644
index 0000000..5794642
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/util/Consts.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea.util;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class Consts {
+ public static final String ERROR = "Error communicating to the server";
+ public static final String UNSPEC_PROJ_ID = "--";
+
+ public interface Url {
+ String DEFAULT = "https://git2.elfuego.biz:444";
+ }
+
+ public interface EndPoint {
+ String API = "/api/v1";
+ String ME = "/user";
+ String REPOS = "/repos/";
+ String ISSUES = "/issues";
+ String COMMENTS = "/comments";
+ String REPOS_SEARCH = REPOS + "search?uid=";
+ }
+
+ public enum States {
+ OPEN, CLOSED;
+ }
+
+ public interface TaskFields {
+ static final String ID = "id";
+ static final String TITLE = "title";
+ static final String DESCRIPTION = "body";
+ static final String CREATEDAT = "created_at";
+ static final String UPDATEDAT = "updated_at";
+ static final String STATE = "state";
+ static final String ASSIGNEE = "assignee";
+ }
+
+ public interface CommentFields {
+ static final String DATE = "updated_at";
+ static final String TEXT = "body";
+ static final String USER = "user";
+ static final String FULLNAME = "full_name";
+ static final String USERNAME = "username";
+ }
+}
diff --git a/src/biz/elfuego/idea/issues/gitea/util/Utils.java b/src/biz/elfuego/idea/issues/gitea/util/Utils.java
new file mode 100644
index 0000000..e8451e3
--- /dev/null
+++ b/src/biz/elfuego/idea/issues/gitea/util/Utils.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright © 2018 by elfuego.biz
+ */
+package biz.elfuego.idea.issues.gitea.util;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * @author Roman Pedchenko
+ * @date 2018.06.30
+ */
+public class Utils {
+ private static final String OK = "ok";
+ private static final String DATA = "data";
+
+ public static String getString(JsonObject object, String fieldName, String defaultValue) {
+ String result = defaultValue;
+ if (object.has(fieldName) && object.get(fieldName).isJsonPrimitive()) {
+ result = object.get(fieldName).getAsJsonPrimitive().getAsString();
+ }
+
+ return result;
+ }
+
+ public static Date getDate(JsonObject object, String fieldName) {
+ Date result = null;
+ if (object.has(fieldName) && object.get(fieldName).isJsonPrimitive()) {
+ result = parseDateISO8601(object.get(fieldName).getAsJsonPrimitive().getAsString());
+ }
+ return result;
+ }
+
+ private static Date parseDateISO8601(String input) {
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
+ if (input.endsWith("Z")) {
+ input = input.substring(0, input.length() - 1) + "GMT-00:00";
+ } else {
+ int inset = 6;
+
+ String s0 = input.substring(0, input.length() - inset);
+ String s1 = input.substring(input.length() - inset, input.length());
+
+ input = s0 + "GMT" + s1;
+ }
+ try {
+ return df.parse(input);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public static JsonObject getObject(JsonElement el) throws Exception {
+ if (el.isJsonObject()) {
+ return el.getAsJsonObject();
+ }
+ throw new Exception(Consts.ERROR + ", getObject: " + el.toString());
+ }
+
+ public static JsonObject getObject(JsonObject object, String fieldName) throws Exception {
+ if (object.has(fieldName) && object.get(fieldName).isJsonObject()) {
+ return object.get(fieldName).getAsJsonObject();
+ }
+ throw new Exception(Consts.ERROR + ", getObject2: " + object.toString());
+ }
+
+ public static JsonArray getArray(JsonElement el) throws Exception {
+ if (el.isJsonArray()) {
+ return el.getAsJsonArray();
+ }
+ throw new Exception(Consts.ERROR + ", getArray: " + el.toString());
+ }
+
+ public static JsonArray getOkData(JsonElement el) throws Exception {
+ if (el.isJsonObject()) {
+ JsonObject obj = el.getAsJsonObject();
+ if (obj.has(OK) && obj.get(OK).isJsonPrimitive() && obj.get(OK).getAsBoolean()) {
+ if (obj.has(DATA) && obj.get(DATA).isJsonArray())
+ return obj.get(DATA).getAsJsonArray();
+ }
+ }
+ throw new Exception(Consts.ERROR + ", getOkData: " + el.toString());
+ }
+}