feat: add GitHub Actions CI and semantic-release (#4)

* Add GitHub Actions CI and semantic-release (#3)

* chore(release): 1.0.0 [skip ci]

# 1.0.0 (2024-03-16)

### Features

* add initial code ([#2](https://github.com/siuhui/async-postgres-watcher/issues/2)) ([6c868e1](https://github.com/siuhui/async-postgres-watcher/commit/6c868e108e8b68e6aacde833a4afe96f75345fe8)), closes [#1](https://github.com/siuhui/async-postgres-watcher/issues/1)

---------

Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
diff --git a/.github/semantic.yml b/.github/semantic.yml
new file mode 100644
index 0000000..96f9233
--- /dev/null
+++ b/.github/semantic.yml
@@ -0,0 +1,2 @@
+# Always validate the PR title AND all the commits
+titleAndCommits: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..2e844c7
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,133 @@
+name: tests
+on:
+  push:
+    branches: [master]
+  pull_request:
+    branches: [master]
+jobs:
+  tests:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: ["3.9", "3.10", "3.11", "3.12"]
+        os: [ubuntu-latest, macOS-latest, windows-latest]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+
+      - name: Set up PostgreSQL on Linux
+        if: startsWith(runner.os, 'linux')
+        run: |
+          sudo systemctl start postgresql.service
+          pg_isready
+          sudo -u postgres psql --command="ALTER USER postgres WITH PASSWORD '123456'" --command="\du"
+
+      - name: Setup PostgreSQL on macOS
+        if: startsWith(runner.os, 'macos')
+        run: |
+          brew services start postgresql
+          echo "Check PostgreSQL service is running"
+          i=10
+          COMMAND='pg_isready'
+          while [ $i -gt 0 ]; do
+              echo "Check PostgreSQL service status"
+              eval $COMMAND && break
+              ((i--))
+              if [ $i == 0 ]; then
+                  echo "PostgreSQL service not ready, all attempts exhausted"
+                  exit 1
+              fi
+              echo "PostgreSQL service not ready, wait 10 more sec, attempts left: $i"
+              sleep 10
+          done
+          psql --command="CREATE USER postgres PASSWORD '123456'" --command="\du" postgres
+
+      - name: Start PostgreSQL on Windows
+        if: startsWith(runner.os, 'windows')
+        run: |
+          $pgService = Get-Service -Name postgresql*
+          Set-Service -InputObject $pgService -Status running -StartupType automatic
+          Start-Process -FilePath "$env:PGBIN\pg_isready" -Wait -PassThru
+          & $env:PGBIN\psql --command="ALTER USER postgres WITH PASSWORD '123456'" --command="\du"
+
+      - name: Upgrade install tools
+        run: python -m pip install --upgrade setuptools wheel
+
+      - name: Install dependencies
+        run: |
+          pip install -r requirements.txt
+
+      - name: Run tests
+        run: |
+          python -m unittest discover -s tests -t tests
+
+  coveralls:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.9
+
+      - name: Install dependencies
+        run: |
+          pip install -r requirements.txt
+          pip install coveralls
+          pip install coverage
+
+      - name: Set up PostgreSQL on Linux
+        if: startsWith(runner.os, 'linux')
+        run: |
+          sudo systemctl start postgresql.service
+          pg_isready
+          sudo -u postgres psql --command="ALTER USER postgres WITH PASSWORD '123456'" --command="\du"
+
+      - name: Run tests
+        run: coverage run -m unittest discover -s tests -t tests
+
+      - name: Upload coverage data to coveralls.io
+        run: coveralls --service=github
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+  release:
+    name: Release
+    runs-on: ubuntu-latest
+    needs: [ tests, coveralls ]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v1
+        with:
+          node-version: '20'
+
+      - name: Setup
+        run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi
+
+      - name: Set up python
+        uses: actions/setup-python@v2
+        with:
+          python-version: 3.9
+
+      - name: Install setuptools
+        run: python -m pip install --upgrade setuptools wheel twine
+
+      - name: Release
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
+        run: npx semantic-release
diff --git a/.releaserc.json b/.releaserc.json
new file mode 100644
index 0000000..94e24cd
--- /dev/null
+++ b/.releaserc.json
@@ -0,0 +1,23 @@
+{
+  "branches": "master",
+  "plugins": [
+    "@semantic-release/commit-analyzer",
+    "@semantic-release/release-notes-generator",
+    "semantic-release-pypi",
+    "@semantic-release/github",
+    [
+      "@semantic-release/changelog",
+      {
+        "changelogFile": "CHANGELOG.md",
+        "changelogTitle": "# Semantic Versioning Changelog"
+      }
+    ],
+	  [
+      "@semantic-release/git",
+      {
+        "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}",
+        "assets": ["CHANGELOG.md", "pyproject.toml"]
+      }
+    ]
+  ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..1257f05
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Semantic Versioning Changelog
+
+# 1.0.0 (2024-03-16)
+
+
+### Features
+
+* add initial code ([#2](https://github.com/siuhui/async-postgres-watcher/issues/2)) ([6c868e1](https://github.com/siuhui/async-postgres-watcher/commit/6c868e108e8b68e6aacde833a4afe96f75345fe8)), closes [#1](https://github.com/siuhui/async-postgres-watcher/issues/1)
diff --git a/README.md b/README.md
index 2b13659..97eceaf 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,20 @@
 # async-postgres-watcher
 
+[![tests](https://github.com/pycasbin/async-postgres-watcher/actions/workflows/release.yml/badge.svg)](https://github.com/pycasbin/async-postgres-watcher/actions)
+[![PyPI - Version](https://img.shields.io/pypi/v/casbin-async-postgres-watcher)](https://pypi.org/project/casbin-async-postgres-watcher/)
+[![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin-async-postgres-watcher.svg)](https://pypi.org/project/casbin-async-postgres-watcher/)
+[![PyPI - Downloads](https://img.shields.io/pypi/dm/casbin-async-postgres-watcher)](https://pypi.org/project/casbin-async-postgres-watcher/)
+[![PyPI - License](https://img.shields.io/pypi/l/casbin-async-postgres-watcher)](https://pypi.org/project/casbin-async-postgres-watcher/)
 [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN)
 
 Async casbin role watcher to be used for monitoring updates to casbin policies
 
+## Installation
+
+```bash
+pip install casbin-async-postgres-watcher
+```
+
 ## Basic Usage Example
 
 ### With Flask-authz
@@ -39,8 +50,8 @@
 # If check_hostname is True, the SSL context is created with sslmode=verify-full.
 # If check_hostname is False, the SSL context is created with sslmode=verify-ca.
 watcher = AsyncPostgresWatcher(host=HOST, port=PORT, user=USER, password=PASSWORD, dbname=DBNAME, sslrootcert=SSLROOTCERT, check_hostname = True, sslcert=SSLCERT, sslkey=SSLKEY)
-
 watcher.set_update_callback(casbin_enforcer.e.load_policy)
+
 casbin_enforcer.set_watcher(watcher)
 ```
 
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d533f02
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+    "devDependencies": {
+        "@semantic-release/changelog": "^6.0.3",
+        "@semantic-release/git": "^10.0.1",
+        "semantic-release": "^22.0.5",
+        "semantic-release-pypi": "^3.0.0"
+    }
+}
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3333e00
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,32 @@
+[project]
+name = "casbin-async-postgres-watcher"
+version = "1.0.0"
+authors = [
+    {name = "hsluoyz", email = "hsluoyz@gmail.com"},
+]
+description = "Async casbin role watcher to be used for monitoring updates to policies for PyCasbin"
+readme = "README.md"
+dynamic = ["dependencies"]
+requires-python = ">=3.5"
+license = {text = "Apache 2.0"}
+classifiers = [
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "License :: OSI Approved :: Apache Software License",
+    "Operating System :: OS Independent",
+]
+
+[project.urls]
+"Home-page" = "https://github.com/pycasbin/async-postgres-watcher"
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools.packages.find]
+exclude = ["tests", "tests.*"]
+
+[tool.setuptools.dynamic]
+dependencies = {file = ["requirements.txt"]}
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 27abd07..61c4c9b 100644
--- a/requirements.txt
+++ b/requirements.txt
Binary files differ
diff --git a/test/test_async_postgres_watcher.py b/tests/test_async_postgres_watcher.py
similarity index 100%
rename from test/test_async_postgres_watcher.py
rename to tests/test_async_postgres_watcher.py