+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5568c61
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,24 @@
+scripts_dir := ./scripts
+
+all: setup deploy
+
+clean:
+ rm .env
+
+deploy:
+ doctl serverless deploy "${PWD}"
+
+list:
+ doctl serverless functions list
+
+logs:
+ doctl serverless activations logs --follow
+
+setup:
+ $(scripts_dir)/setup.sh
+
+test:
+ $(scripts_dir)/test.sh
+
+watch: deploy
+ doctl serverless watch "${PWD}"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..638e141
--- /dev/null
+++ b/README.md
@@ -0,0 +1,168 @@
+
+
+
+
+
+ Kibisis API
+
+
+
+ The Kibisis API is a set of DigitalOcean functions that act as a RESTful API for the Kibisis ecosystem.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#### Table of contents
+
+* [1. Overview](#-1-overview)
+ - [1.1. Project structure](#11-project-structure)
+* [2. Development](#-2-development)
+ - [2.1. Requirements](#21-requirements)
+ - [2.2. Setup `doctl`](#22-setup-doctl)
+ - [2.3. Create a personal namespace (optional)](#23-create-a-personal-namespace-optional)
+ - [2.4. Setting up environment variables](#24-setting-up-environment-variables)
+ - [Deploy the functions to the namespace](#25-deploy-the-functions-to-the-namespace)
+* [3. Appendix](#-3-appendix)
+ - [3.1. Useful commands](#31-useful-commands)
+* [4. How To Contribute](#-4-how-to-contribute)
+* [5. License](#-5-license)
+
+## ποΈ 1. Overview
+
+### 1.1. Project structure
+
+The project structure is based on the [DigitalOcean functions][https://docs.digitalocean.com/products/functions/how-to/structure-projects/] project structure.
+However, the core directories `lib` and `packages` have specific functionality:
+
+* `packages` - This is where each function resides. Each package/function relates to the API path. For example, `achievements/quest` will correspond to an API path `https:///achievements/quests`
+* `lib` - This contains is independent modules that are referenced from the packages.
+
+> β οΈ **NOTE:** Each path must parse the request header and handle the method, i.e. GET, POST, DELETE e.t.c.
+
+[Back to top ^][table-of-contents]
+
+## π οΈ 2. Development
+
+### 2.1. Requirements
+
+* [`doctl`][doctl]
+* [Golang v1.20+][golang]
+* [Make][make]
+
+[Back to top ^][table-of-contents]
+
+### 2.2. Setup `doctl`
+
+The DigitalOcean CLI client, `doctl`, is used to deploy the function to a remote environment tha can be used to develop.
+
+Follow the instructions outlined in the [documentation][doctl].
+
+> β οΈ **NOTE:** Once you have setup `doctl` make sure you also install the serverless subcommand using `doctl serverless install`
+
+[Back to top ^][table-of-contents]
+
+### 2.3. Create a personal namespace (optional)
+
+> β οΈ **NOTE:** If you have a personal namespace already setup, you can skip this step.
+
+1. Setup a personal namespace using your name suffixed by the word "`namespace`" ensuring to use kebab-case:
+
+```shell script
+doctl serverless namespaces create --label="kieran-namespace" --region="ams3"
+```
+
+> β οΈ **NOTE:** The above example shows setting up a namespace in the Amsterdam region, but it is better if you use a region that is closest to you. Run the command: `doctl serverless namespaces list-regions` to get a list of regions and replace the `--region` value with the desired region form the list.
+
+[Back to top ^][table-of-contents]
+
+### 2.4. Setting up environment variables
+
+1. Create a `.env` file from the `.env.example`:
+```shell script
+make setup
+```
+
+2. Edit the values in the `.env` file.
+
+[Back to top ^][table-of-contents]
+
+### 2.5. Deploy the functions to the namespace
+
+1. Deploy the function to the namespace that was created in the [previous](#23-create-a-personal-namespace-optional) step:
+```shell
+make deploy
+```
+
+2. Get the URL of the deployed function:
+```shell
+doctl serverless functions get / --url
+```
+
+This will return a URL in the form of:
+
+```text
+https://faas-ams3-2a2df116.doserverless.co/api/v1/web///
+```
+
+Use this URL to interact with the API.
+
+[Back to top ^][table-of-contents]
+
+## π 3. Appendix
+
+### 3.1. Useful commands
+
+| Command | Description |
+|---------------|--------------------------------------------------------------------------------------------------------------------|
+| `make` | Creates the `.env` file and deploys the functions to the remote namespace. Intended for development purposes only. |
+| `make clean` | Removes build files and configurations. |
+| `make deploy` | Deploys the functions to the remote namespace. Intended for development purposes only. |
+| `make list` | Lists the deployed functions in the configured namespace. |
+| `make logs` | Outputs the activation logs for the deployed functions. |
+| `make setup` | Creates the `.env` file from an `.env.example` file. |
+| `make test` | Runs the tests for each function. |
+| `make watch` | Watches for code changes and redeploys functions to namespace. |
+
+[Back to top ^][table-of-contents]
+
+## π 4. How To Contribute
+
+Please read the [**Contributing Guide**][contribute] to learn about the development process.
+
+[Back to top ^][table-of-contents]
+
+## π 5. License
+
+Please refer to the [COPYING][copying] file.
+
+[Back to top ^][table-of-contents]
+
+
+[contribute]: ./CONTRIBUTING.md
+[copying]: ./COPYING
+[doctl]: https://docs.digitalocean.com/reference/doctl/how-to/install/
+[golang]: https://go.dev/doc/install
+[make]: https://www.gnu.org/software/make/
+[table-of-contents]: #table-of-contents
diff --git a/assets/icon@128x128.png b/assets/icon@128x128.png
new file mode 100644
index 0000000..a068c08
Binary files /dev/null and b/assets/icon@128x128.png differ
diff --git a/lib/types/go.mod b/lib/types/go.mod
new file mode 100644
index 0000000..49cefd9
--- /dev/null
+++ b/lib/types/go.mod
@@ -0,0 +1,3 @@
+module types
+
+go 1.20
diff --git a/lib/types/headers.go b/lib/types/headers.go
new file mode 100644
index 0000000..12ff550
--- /dev/null
+++ b/lib/types/headers.go
@@ -0,0 +1,10 @@
+package types
+
+type Headers struct {
+ Accept string `json:"accept"`
+ AcceptEncoding string `json:"accept-encoding"`
+ UserAgent string `json:"user-agent"`
+ XForwardedFor string `json:"x-forwarded-for"`
+ XForwardedProto string `json:"x-forwarded-proto"`
+ XRequestId string `json:"x-request-id"`
+}
diff --git a/lib/types/http.go b/lib/types/http.go
new file mode 100644
index 0000000..21176fa
--- /dev/null
+++ b/lib/types/http.go
@@ -0,0 +1,7 @@
+package types
+
+type Http struct {
+ Headers Headers `json:"headers"`
+ Method string `json:"method"`
+ Path string `json:"path"`
+}
diff --git a/packages/achievements/quests/go.mod b/packages/achievements/quests/go.mod
new file mode 100644
index 0000000..8a686b5
--- /dev/null
+++ b/packages/achievements/quests/go.mod
@@ -0,0 +1,11 @@
+module quests
+
+go 1.20
+
+require (
+ types v0.0.0
+)
+
+replace (
+ types v0.0.0 => ../../../lib/types
+)
diff --git a/packages/achievements/quests/internal/types/request.go b/packages/achievements/quests/internal/types/request.go
new file mode 100644
index 0000000..46a0957
--- /dev/null
+++ b/packages/achievements/quests/internal/types/request.go
@@ -0,0 +1,8 @@
+package types
+
+import "types"
+
+type Request struct {
+ Http types.Http `json:"http,omitempty"`
+ Name string `json:"name"`
+}
diff --git a/packages/achievements/quests/main.go b/packages/achievements/quests/main.go
new file mode 100644
index 0000000..5eb8871
--- /dev/null
+++ b/packages/achievements/quests/main.go
@@ -0,0 +1,13 @@
+package main
+
+import (
+ "fmt"
+ internaltypes "quests/internal/types"
+)
+
+func Main(request internaltypes.Request) string {
+ fmt.Println(fmt.Sprintf("method %s", request.Http.Method))
+ fmt.Println(fmt.Sprintf("name %s", request.Name))
+
+ return "Hello " + request.Name + request.Http.Method
+}
diff --git a/packages/system/versions/VERSION b/packages/system/versions/VERSION
new file mode 100644
index 0000000..3eefcb9
--- /dev/null
+++ b/packages/system/versions/VERSION
@@ -0,0 +1 @@
+1.0.0
diff --git a/packages/system/versions/go.mod b/packages/system/versions/go.mod
new file mode 100644
index 0000000..a037996
--- /dev/null
+++ b/packages/system/versions/go.mod
@@ -0,0 +1,3 @@
+module versions
+
+go 1.20
diff --git a/packages/system/versions/internal/types/response.go b/packages/system/versions/internal/types/response.go
new file mode 100644
index 0000000..c90f149
--- /dev/null
+++ b/packages/system/versions/internal/types/response.go
@@ -0,0 +1,6 @@
+package types
+
+type Response struct {
+ Body ResponseBody `json:"body,omitempty"`
+ StatusCode int `json:"statusCode,omitempty"`
+}
diff --git a/packages/system/versions/internal/types/responsebody.go b/packages/system/versions/internal/types/responsebody.go
new file mode 100644
index 0000000..0e1fb27
--- /dev/null
+++ b/packages/system/versions/internal/types/responsebody.go
@@ -0,0 +1,6 @@
+package types
+
+type ResponseBody struct {
+ APIVersion string `json:"apiVersion"`
+ Environment string `json:"environment"`
+}
diff --git a/packages/system/versions/main.go b/packages/system/versions/main.go
new file mode 100644
index 0000000..2da1db2
--- /dev/null
+++ b/packages/system/versions/main.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ _ "embed"
+ "net/http"
+ "os"
+ "strings"
+ internaltypes "versions/internal/types"
+)
+
+//go:embed VERSION
+var version string
+
+func Main() *internaltypes.Response {
+ return &internaltypes.Response{
+ Body: internaltypes.ResponseBody{
+ APIVersion: strings.TrimSpace(version),
+ Environment: os.Getenv("ENVIRONMENT"),
+ },
+ StatusCode: http.StatusOK,
+ }
+}
diff --git a/packages/system/versions/main_test.go b/packages/system/versions/main_test.go
new file mode 100644
index 0000000..c1726c0
--- /dev/null
+++ b/packages/system/versions/main_test.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "net/http"
+ "os"
+ "testing"
+)
+
+func TestMainWithSuccessfulResponse(t *testing.T) {
+ err := os.Setenv("ENVIRONMENT", "development")
+ if err != nil {
+ t.Errorf("failed to set \"environment\" variable")
+ }
+
+ response := Main()
+
+ if response.Body.Environment != "development" {
+ t.Errorf("expected \"environment\" to be \"development\", got %s", response.Body.Environment)
+ }
+ if response.StatusCode != http.StatusOK {
+ t.Errorf("expected \"statusCode\" to be \"%d\", got %d", http.StatusOK, response.StatusCode)
+ }
+}
diff --git a/project.yml b/project.yml
new file mode 100644
index 0000000..5d4b790
--- /dev/null
+++ b/project.yml
@@ -0,0 +1,22 @@
+environment:
+ ENVIRONMENT: "${ENVIRONMENT}"
+packages:
+ - name: achievements
+ shared: false
+ functions:
+ - name: quests
+ binary: true
+ main: "main"
+ runtime: go:1.20
+ web: true
+ webSecure: false
+
+ - name: system
+ shared: false
+ functions:
+ - name: versions
+ binary: true
+ main: "main"
+ runtime: go:1.20
+ web: true
+ webSecure: false
diff --git a/scripts/set_vars.sh b/scripts/set_vars.sh
new file mode 100755
index 0000000..02f6ce8
--- /dev/null
+++ b/scripts/set_vars.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+# Public: Convenience function that exports some common environment variables.
+function set_vars() {
+ export ERROR_PREFIX='\033[0;31m[ERROR]\033[0m'
+ export INFO_PREFIX='\033[1;33m[INFO]\033[0m'
+}
diff --git a/scripts/setup.sh b/scripts/setup.sh
new file mode 100755
index 0000000..36120a6
--- /dev/null
+++ b/scripts/setup.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$(dirname "${0}")
+
+source "${SCRIPT_DIR}"/set_vars.sh
+
+# Public: Creates a .env file if it don't exist.
+#
+# Examples
+#
+# ./scripts/setup.sh
+#
+# Returns exit code 1 if no example file exists, otherwise, exit code 0 is returned.
+function main() {
+ local env_example_file_path
+ local env_file_path
+
+ set_vars
+
+ env_example_file_path="${PWD}/.env.example"
+
+ if [[ ! -f "${env_example_file_path}" ]];
+ then
+ printf "%b no .env example at %b \n" "${ERROR_PREFIX}" "${env_example_file_path}"
+ exit 1
+ fi
+
+ env_file_path="${PWD}/.env"
+
+ printf "%b creating %b files...\n" "${INFO_PREFIX}" "${env_file_path}"
+
+ # create the .env file
+ cp -n "${env_example_file_path}" "${env_file_path}"
+
+ printf "%b done!\n" "${INFO_PREFIX}"
+
+ exit 0
+}
+
+# and so, it begins...
+main
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100755
index 0000000..54064b7
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$(dirname "${0}")
+
+source "${SCRIPT_DIR}"/set_vars.sh
+
+# Public: Iterates through each function and runs the tests.
+#
+# Examples
+#
+# ./bin/test.sh
+#
+# Returns exit code 0.
+function main {
+ local function_dir
+ local package_dir
+
+ set_vars
+
+ for package_dir in ./packages/*/; do
+ for function_dir in "${package_dir}"/*/; do
+ # remove the trailing "/"
+ function_dir=${function_dir%*/}
+
+ # change directory to the function directory and run the tests
+ (cd "${function_dir}" && go test -v)
+ done
+ done
+
+ exit 0
+}
+
+# And so, it begins...
+main
diff --git a/scripts/update_version.sh b/scripts/update_version.sh
new file mode 100755
index 0000000..0d38342
--- /dev/null
+++ b/scripts/update_version.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+
+SCRIPT_DIR=$(dirname "${0}")
+
+source "${SCRIPT_DIR}"/set_vars.sh
+
+# Public: Updates the ./packages/system/versions/VERSION file with the supplied version.
+#
+# $1 - the version to update
+#
+# Examples
+
+# ./scripts/update_version_file.sh "1.2.3"
+#
+# Returns exit code 0 if successful, or 1 if the semantic version is incorrectly formatted.
+function main() {
+ set_vars
+
+ if [ -z "${1}" ]; then
+ printf "%b no version specified, use: ./bin/update_version_file.sh [version] \n" "${ERROR_PREFIX}"
+ exit 1
+ fi
+
+ # check the input is in semantic version format
+ if [[ ! "${1}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
+ printf "%b invalid semantic version, got '${1}', but should be in the format '1.0.0' \n" "${ERROR_PREFIX}"
+ exit 1
+ fi
+
+ # remove the previous contents
+ true > "${PWD}"/packages/system/versions/VERSION
+
+ # use the new version
+ echo "$1" >> "${PWD}"/packages/system/versions/VERSION
+
+ printf "%b new version set to: %b\n" "${INFO_PREFIX}" "$1"
+
+ exit 0
+}
+
+# and so, it begins...
+main "$@"