From db29a4869ccc06b0248c78af0f6711a708da57de Mon Sep 17 00:00:00 2001
From: Zaki <zakiamanullah215@gmail.com>
Date: Sun, 12 Nov 2023 23:51:15 +0700
Subject: [PATCH] chore: general architecture

---
 .env                                          |   6 +
 .gitignore                                    |   5 +-
 .idea/dataSources.xml                         |  12 ++
 .idea/sqldialects.xml                         |   7 +
 .idea/uiDesigner.xml                          | 124 ++++++++++++++++++
 Dockerfile                                    |   2 +-
 db/migrations.sql                             |  14 ++
 docker-compose.yml                            |  11 +-
 pom.xml                                       |  28 ++++
 src/main/java/org/spotiplay/Main.java         |   2 +
 src/main/java/org/spotiplay/db/Database.java  |  41 ++++++
 .../org/spotiplay/db/DatabaseInterface.java   |   6 +
 .../java/org/spotiplay/model/Logging.java     |  61 +++++++++
 src/main/java/org/spotiplay/model/Model.java  |   5 +
 .../org/spotiplay/model/Subscription.java     |  71 ++++++++++
 .../spotiplay/model/SubscriptionStatus.java   |  19 +++
 .../org/spotiplay/repo/LoggingRepository.java |  40 ++++++
 .../java/org/spotiplay/repo/Repository.java   |  36 +++++
 .../repo/SubscriptionRepository.java          | 111 ++++++++++++++++
 src/main/java/org/spotiplay/utils/Dotenv.java |  29 ++++
 .../spotiplay/ws/SubscriptionInterface.java   |   8 ++
 .../java/org/spotiplay/ws/WebService.java     |   9 ++
 22 files changed, 644 insertions(+), 3 deletions(-)
 create mode 100644 .env
 create mode 100644 .idea/dataSources.xml
 create mode 100644 .idea/sqldialects.xml
 create mode 100644 .idea/uiDesigner.xml
 create mode 100644 db/migrations.sql
 create mode 100644 src/main/java/org/spotiplay/db/Database.java
 create mode 100644 src/main/java/org/spotiplay/db/DatabaseInterface.java
 create mode 100644 src/main/java/org/spotiplay/model/Logging.java
 create mode 100644 src/main/java/org/spotiplay/model/Model.java
 create mode 100644 src/main/java/org/spotiplay/model/Subscription.java
 create mode 100644 src/main/java/org/spotiplay/model/SubscriptionStatus.java
 create mode 100644 src/main/java/org/spotiplay/repo/LoggingRepository.java
 create mode 100644 src/main/java/org/spotiplay/repo/Repository.java
 create mode 100644 src/main/java/org/spotiplay/repo/SubscriptionRepository.java
 create mode 100644 src/main/java/org/spotiplay/utils/Dotenv.java
 create mode 100644 src/main/java/org/spotiplay/ws/SubscriptionInterface.java
 create mode 100644 src/main/java/org/spotiplay/ws/WebService.java

diff --git a/.env b/.env
new file mode 100644
index 0000000..4a76d71
--- /dev/null
+++ b/.env
@@ -0,0 +1,6 @@
+MYSQL_DATABASE=spotiplay-soap
+MYSQL_USER=kelompok-30
+MYSQL_PASSWORD=kelompok-30
+MYSQL_ROOT_PASSWORD=kelompok-30
+REST_API_KEY=fb791bd8fbf13047b92a2e9819d1a85e09279a878937aebce4654beccff5f66cecc6b93969c6e8b55c24bf4604a93f4c8b1b0f827bfd3d12fb4034c314d8f24e
+PHP_API_KEY=6c15b79f3d06aca57138c9df532de8c64681729c15ad29be4eb2250984bed2292b7f0750dc9195d99fa535ecc16bcd116735ff727981066448e880a228d76942
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5ff6309..ffdf945 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,4 +35,7 @@ build/
 .vscode/
 
 ### Mac OS ###
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+### Environment Files ###
+.env
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..e765bc8
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="Spotiplay SOAP Database" uuid="531f93a0-db1d-4637-a0b5-15246be545fa">
+      <driver-ref>mysql.8</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mysql://host.docker.internal:3306/spotiplay-soap</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..49d36c4
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="SqlDialectMappings">
+    <file url="file://$PROJECT_DIR$/db/migrations.sql" dialect="GenericSQL" />
+    <file url="PROJECT" dialect="MySQL" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.svg" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.svg" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.svg" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.svg" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+</project>
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 48fa6fb..b2142e4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,4 +6,4 @@ COPY target/spotiplay-soap-1.0-SNAPSHOT.jar /app/spotiplay-soap-1.0-SNAPSHOT.jar
 
 EXPOSE 8080
 
-ENTRYPOINT ["java", "-jar", "spotiplay-soap-1.0-SNAPSHOT.jar"]
\ No newline at end of file
+ENTRYPOINT ["java", "-jar", "spotiplay-soap-1.0-SNAPSHOT-jar-with-dependencies.jar"]
\ No newline at end of file
diff --git a/db/migrations.sql b/db/migrations.sql
new file mode 100644
index 0000000..7d9acc8
--- /dev/null
+++ b/db/migrations.sql
@@ -0,0 +1,14 @@
+CREATE TABLE logging (
+    id SERIAL PRIMARY KEY,
+    description TEXT NOT NULL,
+    ip CHAR(255) NOT NULL,
+    endpoint VARCHAR(255) NOT NULL,
+    requested_at TIMESTAMP NOT NULL DEFAULT NOW() NOT NULL
+);
+
+CREATE TABLE subscriptions (
+    creator_id SERIAL PRIMARY KEY,
+    user_id INTEGER NOT NULL,
+    STATUS ENUM('pending', 'accepted', 'rejected') NOT NULL DEFAULT 'pending'
+);
+
diff --git a/docker-compose.yml b/docker-compose.yml
index 8903ddf..0fb671e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -7,4 +7,13 @@ services:
     ports:
       - "8080:8080"
     volumes:
-      - ./target/spotiplay-soap-1.0-SNAPSHOT.jar:/app/spotiplay-soap-1.0-SNAPSHOT.jar
\ No newline at end of file
+      - ./target/spotiplay-soap-1.0-SNAPSHOT.jar:/app/spotiplay-soap-1.0-SNAPSHOT.jar
+      - ./target/spotiplay-soap-1.0-SNAPSHOT-jar-with-dependencies.jar:/app/spotiplay-soap-1.0-SNAPSHOT-jar-with-dependencies.jar
+  soap-db:
+    image: mysql:5.7
+    ports:
+      - "3306:3306"
+    env_file:
+      - .env
+    volumes:
+      - ./db:/docker-entrypoint-initdb.d
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4c6cdf1..50adbd6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,11 @@
             <artifactId>jaxws-rt</artifactId>
             <version>2.3.5</version>
         </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.23</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -31,6 +36,29 @@
                     </archive>
                 </configuration>
             </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.3.0</version> <!-- Use the latest version available -->
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.spotiplay.Main</mainClass> <!-- Replace with your main class -->
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for an identifier -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/src/main/java/org/spotiplay/Main.java b/src/main/java/org/spotiplay/Main.java
index b1e0d3c..15dc478 100644
--- a/src/main/java/org/spotiplay/Main.java
+++ b/src/main/java/org/spotiplay/Main.java
@@ -1,5 +1,7 @@
 package org.spotiplay;
 
+import org.spotiplay.db.Database;
+
 import javax.xml.ws.Endpoint;
 
 public class Main {
diff --git a/src/main/java/org/spotiplay/db/Database.java b/src/main/java/org/spotiplay/db/Database.java
new file mode 100644
index 0000000..aebd9e3
--- /dev/null
+++ b/src/main/java/org/spotiplay/db/Database.java
@@ -0,0 +1,41 @@
+package org.spotiplay.db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+public class Database implements DatabaseInterface {
+    private static Database instance = null;
+    private Connection connection;
+
+    private Database() {
+        String url = "jdbc:mysql://host.docker.internal:3306/spotiplay-soap?useSSL=false";
+        String username = "kelompok-30";
+        String password = "kelompok-30";
+        try {
+            connection = DriverManager.getConnection(url, username, password);
+        } catch (SQLException e) {
+            System.out.println("SQLException: " + e.getMessage());
+            System.out.println("SQLState: " + e.getSQLState());
+            System.out.println("VendorError: " + e.getErrorCode());
+            e.printStackTrace();
+            System.exit(1);
+        } catch (Exception e) {
+            System.out.println("Error: " + e.getMessage());
+            e.printStackTrace();
+            System.exit(1);
+        }
+    }
+
+    @Override
+    public Connection getConnection() {
+        return connection;
+    }
+
+    public static Database getInstance() {
+        if (instance == null) {
+            instance = new Database();
+        }
+        return instance;
+    }
+}
diff --git a/src/main/java/org/spotiplay/db/DatabaseInterface.java b/src/main/java/org/spotiplay/db/DatabaseInterface.java
new file mode 100644
index 0000000..e9e1e24
--- /dev/null
+++ b/src/main/java/org/spotiplay/db/DatabaseInterface.java
@@ -0,0 +1,6 @@
+package org.spotiplay.db;
+
+import java.sql.Connection;
+public interface DatabaseInterface {
+    public Connection getConnection();
+}
diff --git a/src/main/java/org/spotiplay/model/Logging.java b/src/main/java/org/spotiplay/model/Logging.java
new file mode 100644
index 0000000..0a27944
--- /dev/null
+++ b/src/main/java/org/spotiplay/model/Logging.java
@@ -0,0 +1,61 @@
+package org.spotiplay.model;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class Logging extends Model {
+    private String description;
+    private String ipAddress;
+    private String endpoint;
+    private String requestedAt;
+
+    public Logging(String description, String ipAddress, String endpoint, String requestedAt) {
+        this.description = description;
+        this.ipAddress = ipAddress;
+        this.endpoint = endpoint;
+        this.requestedAt = requestedAt;
+    }
+
+    public Logging() {
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public String getEndpoint() {
+        return endpoint;
+    }
+
+    public void setEndpoint(String endpoint) {
+        this.endpoint = endpoint;
+    }
+
+    public String getRequestedAt() {
+        return requestedAt;
+    }
+
+    public void setRequestedAt(String requestedAt) {
+        this.requestedAt = requestedAt;
+    }
+
+    @Override
+    public void constructFromSQLResult(ResultSet result) throws SQLException {
+        this.description = result.getString("description");
+        this.ipAddress = result.getString("ip_address");
+        this.endpoint = result.getString("endpoint");
+        this.requestedAt = result.getString("requested_at");
+    }
+}
diff --git a/src/main/java/org/spotiplay/model/Model.java b/src/main/java/org/spotiplay/model/Model.java
new file mode 100644
index 0000000..107e889
--- /dev/null
+++ b/src/main/java/org/spotiplay/model/Model.java
@@ -0,0 +1,5 @@
+package org.spotiplay.model;
+
+public abstract class Model {
+    public abstract void constructFromSQLResult(java.sql.ResultSet result) throws java.sql.SQLException;
+}
diff --git a/src/main/java/org/spotiplay/model/Subscription.java b/src/main/java/org/spotiplay/model/Subscription.java
new file mode 100644
index 0000000..64582ee
--- /dev/null
+++ b/src/main/java/org/spotiplay/model/Subscription.java
@@ -0,0 +1,71 @@
+package org.spotiplay.model;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class Subscription extends Model {
+
+    private Integer creatorId;
+    private Integer userId;
+    private SubscriptionStatus status;
+
+    public Subscription(Integer creatorId, Integer userId, SubscriptionStatus status) {
+        this.creatorId = creatorId;
+        this.userId = userId;
+        this.status = status;
+    }
+
+    public Subscription() {
+    }
+
+    public Integer getCreatorId() {
+        return creatorId;
+    }
+
+    public void setCreatorId(Integer creatorId) {
+        this.creatorId = creatorId;
+    }
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public SubscriptionStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(SubscriptionStatus status) {
+        this.status = status;
+    }
+
+    @Override
+    public void constructFromSQLResult(ResultSet result) throws SQLException {
+        this.creatorId = result.getInt("creator_id");
+        this.userId = result.getInt("user_id");
+        this.status = SubscriptionStatus.fromString(result.getString("status"));
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        Subscription that = (Subscription) o;
+
+        if (!creatorId.equals(that.creatorId)) return false;
+        if (!userId.equals(that.userId)) return false;
+        return status == that.status;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = creatorId.hashCode();
+        result = 31 * result + userId.hashCode();
+        result = 31 * result + status.hashCode();
+        return result;
+    }
+}
diff --git a/src/main/java/org/spotiplay/model/SubscriptionStatus.java b/src/main/java/org/spotiplay/model/SubscriptionStatus.java
new file mode 100644
index 0000000..92e58c0
--- /dev/null
+++ b/src/main/java/org/spotiplay/model/SubscriptionStatus.java
@@ -0,0 +1,19 @@
+package org.spotiplay.model;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum SubscriptionStatus {
+    PENDING,
+    ACCEPTED,
+    REJECTED;
+
+    public static SubscriptionStatus fromString(String value) {
+        for (SubscriptionStatus status : SubscriptionStatus.values()) {
+            if (status.toString().equalsIgnoreCase(value)) {
+                return status;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/spotiplay/repo/LoggingRepository.java b/src/main/java/org/spotiplay/repo/LoggingRepository.java
new file mode 100644
index 0000000..0f692c4
--- /dev/null
+++ b/src/main/java/org/spotiplay/repo/LoggingRepository.java
@@ -0,0 +1,40 @@
+package org.spotiplay.repo;
+
+import org.spotiplay.db.Database;
+import org.spotiplay.model.Logging;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class LoggingRepository extends Repository<Logging> {
+    private static LoggingRepository instance;
+
+    public LoggingRepository(Database database, String tableName) {
+        super(database, tableName);
+    }
+
+    @Override
+    public Repository<Logging> getInstance() {
+        if (instance == null) {
+            instance = new LoggingRepository(Database.getInstance(), "logging");
+        }
+        return instance;
+    }
+
+    @Override
+    public Logging create(Logging logging) throws Exception{
+        Connection connection = database.getConnection();
+        Statement stmt = connection.createStatement();
+
+        String sql = "INSERT INTO " + tableName + " (description, ip, endpoint, requested_at) VALUES ('" +
+                logging.getDescription() + "', '" + logging.getIpAddress() + "', '" + logging.getEndpoint() + "', '"
+                + logging.getRequestedAt() + "');";
+
+        int res = stmt.executeUpdate(sql);
+        if (res == 0) {
+            throw new Exception("Error: Failed to create log");
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/org/spotiplay/repo/Repository.java b/src/main/java/org/spotiplay/repo/Repository.java
new file mode 100644
index 0000000..4313a89
--- /dev/null
+++ b/src/main/java/org/spotiplay/repo/Repository.java
@@ -0,0 +1,36 @@
+package org.spotiplay.repo;
+
+import org.spotiplay.db.Database;
+
+import java.util.List;
+import java.util.Map;
+
+public abstract class Repository<Model> {
+    protected Database database;
+    protected String tableName;
+
+    public Repository(Database database, String tableName) {
+        this.database = database;
+        this.tableName = tableName;
+    }
+
+    public abstract Repository<Model> getInstance();
+    public List<Model> findAll() throws Exception {
+        throw new Exception("Not implemented");
+    }
+    public Model create(Model model) throws Exception {
+        throw new Exception("Not implemented");
+    }
+    public Model update(Model model) throws Exception {
+        throw new Exception("Not implemented");
+    }
+    public Model delete(Model model) throws Exception {
+        throw new Exception("Not implemented");
+    }
+    public Model findById(Map<String, Integer> id) throws Exception {
+        throw new Exception("Not implemented");
+    }
+    public List<Model> findBy(String field, String value) throws Exception {
+        throw new Exception("Not implemented");
+    }
+}
diff --git a/src/main/java/org/spotiplay/repo/SubscriptionRepository.java b/src/main/java/org/spotiplay/repo/SubscriptionRepository.java
new file mode 100644
index 0000000..33a1b92
--- /dev/null
+++ b/src/main/java/org/spotiplay/repo/SubscriptionRepository.java
@@ -0,0 +1,111 @@
+package org.spotiplay.repo;
+
+import org.spotiplay.db.Database;
+import org.spotiplay.model.Subscription;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class SubscriptionRepository extends Repository<Subscription> {
+    private static SubscriptionRepository instance;
+
+    public SubscriptionRepository(Database database, String tableName) {
+        super(database, tableName);
+    }
+
+    @Override
+    public Repository<Subscription> getInstance() {
+        if (instance == null) {
+            instance = new SubscriptionRepository(Database.getInstance(), "subscription");
+        }
+        return instance;
+    }
+
+    public List<Subscription> findAll() throws SQLException {
+        List<Subscription> result = new ArrayList<>();
+
+        Connection connection = database.getConnection();
+        Statement statement = connection.createStatement();
+        String sql = "SELECT * FROM " + tableName + ";";
+        ResultSet rs = statement.executeQuery(sql);
+        while (rs.next()) {
+            Subscription subscription = new Subscription();
+            subscription.constructFromSQLResult(rs);
+            result.add(subscription);
+        }
+        return result;
+    }
+
+    public Subscription create(Subscription model) throws SQLException {
+        Connection connection = database.getConnection();
+        Statement stmt = connection.createStatement();
+        String sql = "INSERT INTO " + tableName + " (creator_id, user_id) VALUES (" +
+                model.getCreatorId() + ", " + model.getUserId() + ");";
+        int res = stmt.executeUpdate(sql);
+        if (res == 0) {
+            throw new SQLException("Error: Failed to create subscription");
+        }
+        return null;
+    }
+
+    public Subscription update(Subscription model) throws SQLException {
+        Connection connection = database.getConnection();
+        Statement stmt = connection.createStatement();
+        String sql = "UPDATE " + tableName + " SET status = '" + model.getStatus().toString() + "' WHERE creator_id = " +
+                model.getCreatorId() + " AND user_id = " + model.getUserId() + ";";
+        int res = stmt.executeUpdate(sql);
+        if (res == 0) {
+            throw new SQLException("Error: Failed to update subscription");
+        } else if (res > 1) {
+            throw new SQLException("Error: Updated more than one subscription");
+        }
+        return null;
+    }
+
+    public Subscription delete(Subscription model) throws SQLException {
+        Connection connection = database.getConnection();
+        Statement stmt = connection.createStatement();
+        String sql = "DELETE FROM " + tableName + " WHERE creator_id = " + model.getCreatorId() + " AND user_id = " +
+                model.getUserId() + ";";
+        int res = stmt.executeUpdate(sql);
+        if (res == 0) {
+            throw new SQLException("Error: Failed to delete subscription");
+        } else if (res > 1) {
+            throw new SQLException("Error: Deleted more than one subscription");
+        }
+        return null;
+    }
+
+    public Subscription findById(Map<String, Integer> id) throws SQLException {
+        assert id.size() == 2 && id.containsKey("creator_id") && id.containsKey("user_id");
+        Connection connection = database.getConnection();
+        Statement statement = connection.createStatement();
+        String sql = "SELECT * FROM " + tableName + " WHERE creator_id = " + id.get("creator_id") + " AND user_id = " + id.get("user_id") + ";";
+        ResultSet rs = statement.executeQuery(sql);
+        Subscription subscription = new Subscription();
+        subscription.constructFromSQLResult(rs);
+        return subscription;
+    }
+    public List<Subscription> findBy(String field, String value) throws SQLException {
+        List<Subscription> result = new ArrayList<>();
+
+        Connection connection = database.getConnection();
+        Statement statement = connection.createStatement();
+        String sql = "SELECT * FROM " + tableName + " WHERE " + field + " = '" + value + "';";
+        ResultSet rs = statement.executeQuery(sql);
+
+        while (rs.next()) {
+            Subscription subscription = new Subscription();
+            subscription.constructFromSQLResult(rs);
+            result.add(subscription);
+        }
+
+        return result;
+    }
+
+}
diff --git a/src/main/java/org/spotiplay/utils/Dotenv.java b/src/main/java/org/spotiplay/utils/Dotenv.java
new file mode 100644
index 0000000..2e59122
--- /dev/null
+++ b/src/main/java/org/spotiplay/utils/Dotenv.java
@@ -0,0 +1,29 @@
+package org.spotiplay.utils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Dotenv {
+    private static final Map<String, String> env = load();
+    public static Map<String, String> load() {
+        Map<String, String> env = new HashMap<>();
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(".env"));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                String[] parts = line.split("=");
+                env.put(parts[0].trim(), parts[1].trim());
+            }
+            reader.close();
+        } catch (IOException e) {
+            System.out.println("Error: " + e.getMessage());
+        }
+        return env;
+    }
+    public static String get(String key) {
+        return env.get(key);
+    }
+}
diff --git a/src/main/java/org/spotiplay/ws/SubscriptionInterface.java b/src/main/java/org/spotiplay/ws/SubscriptionInterface.java
new file mode 100644
index 0000000..344747c
--- /dev/null
+++ b/src/main/java/org/spotiplay/ws/SubscriptionInterface.java
@@ -0,0 +1,8 @@
+package org.spotiplay.ws;
+
+import javax.jws.WebMethod;
+import javax.jws.WebService;
+
+@WebService
+public interface SubscriptionInterface {
+}
diff --git a/src/main/java/org/spotiplay/ws/WebService.java b/src/main/java/org/spotiplay/ws/WebService.java
new file mode 100644
index 0000000..4fa0ea3
--- /dev/null
+++ b/src/main/java/org/spotiplay/ws/WebService.java
@@ -0,0 +1,9 @@
+package org.spotiplay.ws;
+
+public abstract class WebService {
+    protected void recordClientRequest(String endpoint, String description, String ipAddress) {
+
+    }
+
+
+}
-- 
GitLab