{"id":49453688,"url":"https://github.com/hpi-schul-cloud/gamification","last_synced_at":"2026-06-18T22:00:57.398Z","repository":{"id":49319047,"uuid":"132759935","full_name":"hpi-schul-cloud/gamification","owner":"hpi-schul-cloud","description":"A reusable microservice for gamification.","archived":true,"fork":false,"pushed_at":"2020-02-18T22:05:01.000Z","size":2707,"stargazers_count":43,"open_issues_count":27,"forks_count":16,"subscribers_count":23,"default_branch":"master","last_synced_at":"2024-04-14T03:09:39.563Z","etag":null,"topics":["gamification","gamification-engine"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hpi-schul-cloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-09T13:19:32.000Z","updated_at":"2024-03-26T06:32:05.000Z","dependencies_parsed_at":"2022-09-10T21:50:18.617Z","dependency_job_id":null,"html_url":"https://github.com/hpi-schul-cloud/gamification","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hpi-schul-cloud/gamification","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hpi-schul-cloud%2Fgamification","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hpi-schul-cloud%2Fgamification/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hpi-schul-cloud%2Fgamification/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hpi-schul-cloud%2Fgamification/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hpi-schul-cloud","download_url":"https://codeload.github.com/hpi-schul-cloud/gamification/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hpi-schul-cloud%2Fgamification/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34508867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gamification","gamification-engine"],"created_at":"2026-04-30T04:01:03.703Z","updated_at":"2026-06-18T22:00:57.385Z","avatar_url":"https://github.com/hpi-schul-cloud.png","language":"JavaScript","funding_links":[],"categories":["📦 Legacy \u0026 Inactive Projects"],"sub_categories":[],"readme":"[![Build Status](https://travis-ci.com/schul-cloud/gamification.svg?branch=master)](https://travis-ci.com/schul-cloud/gamification)\n[![Coverage Status](https://coveralls.io/repos/github/schul-cloud/gamification/badge.svg?branch=master)](https://coveralls.io/github/schul-cloud/gamification?branch=master)\n[![GitHub license](https://img.shields.io/github/license/schul-cloud/gamification.svg)](https://github.com/schul-cloud/gamification/blob/master/LICENSE) [![Greenkeeper badge](https://badges.greenkeeper.io/schul-cloud/gamification.svg)](https://greenkeeper.io/)\n\n# Gamification\n\n\u003e A reusable microservice for gamification.\n\nThis project provides a microservice to manage gamification for your\napplication. It does not provide any graphics or pre-defined content, but\nonly a backend REST API. At its core, the service listens to events sent\neither via HTTP POST calls or retrieved from RabbitMQ. Then, a list of\nuser-defined achievement rules is tested against the newly received event.\nShould an achievement rule be evaluated to true, a new achievement is granted\nto the user.\n\n## Terminology\n\n### User\n\nAll gamification data is associated with a user. Users are identified\nby a unique string we commonly call `user_id`. Each user has\n\n- a *level*\n- many granted *achievement*s\n- xp in multiple *xp-pools*\n\nPlease note that there is no explicit user table. User data is distributed\nover multiple other tables and aggregated on demand.\n\n### Achievement\n\nAchievements are the core object of the gamification service. Users strive to\ncollect achievements. Achievements can be thought of as badges, belts, or\nwhatever other representation of progress you can think of. To the gamification service,\nhowever, these are all the same: achievements. Achievements usually define one\nor multiple requirements to be granted, for example:\n\n- the user reached 10 XP\n- the user achieved the FooAchievement\n- a FooEvent is received and the user has less than 42 XP\n\nThe same achievement can be granted multiple times to the same user, if desired.\nAchievements can also be revoked after being granted. This can be used to\nreplace an easy to acquire FooSilver achievement with a harder to get FooGold\nachievement.\nSome achievements may be granted again even after being revoked, others may\nnot be granted again after being revoked.\nThe behaviour can be configured using the `maxAwarded` and `maxAwardedTotal`\nconfiguration options, respectively.\n\n### XP-Pool\n\nThe application may define multiple xp-pools. The default pool is called \"XP\"\nand is always defined. Each user has a number of xp in each xp-pool. A new user has\n0 xp in all xp-pools. XP may be incremented as well as decremented, but should\nnever go below 0. The different xp-pools may be used for purposes such as\n\n- counting some form of e*xp*erience, as is commonly done in games\n- representing some form of a currency the user can spend and earn\n- counting the number of times something happened, i.e. a specific event was\n  received. This counter can then be used in more complex achievement\n  requirements.\n\n### Level\n\nEach user has a level. The user starts at level 1 and can never go below that.\nThe level is strictly tied to the user's main xp \"XP\". The required xp for each\nlevel can be configured to either be linearly increasing, exponentially\nincreasing, or completely custom-defined. The level is not stored anywhere but\nalways calculated on the fly. If the user, for whatever reason, loses xp, their\nlevel may also decrease.\n\n## Workflow\n\nThis section describes the general concept of the gamification service in more\ndetail. The service is designed to be fully event-driven and integrate well with\nRabbitMQ. It is thought to be integrated into your existing microservice\narchitecture. It listens to configurable events emitted by your other services.\nThese events are explicitly not gamification-specific. For example, normally,\nyour application wouldn't send a \"Grant10XP\". Instead, you should send events\nlike \"ForumPost\" or \"UserSignup\". You need to configure rules (see next section)\ndetailing what happens after a \"ForumPost\" or \"UserSignup\" event (e.g., giving\n10 XP). Your existing services do not need to be aware of their events\nbeing used for gamification purposes.\n\nThe gamification service subscribes to a RabbitMQ queue from which it processes\nevents. Whenever the service receives an event, it checks whether or not the\nevent is relevant to gamification. If yes, the service executes any\nconfigured immediate actions, if there are any. Then, it checks all configured\nachievement rules to see whether or not a new achievement should be granted to\nthe user. It is your job to setup the events to listen to, event actions and\nachievement rules for your application as you see fit. Once an achievement or xp\nare granted, they are persisted to MongoDB. It is planned to also send an event\nback to RabbitMQ when an achievement or xp are granted.\n\nThe gamification service also provides a (mostly) read-only REST API, which can\nbe used to ask for a user's achievements, xp and level. In the future, the API\nmay also support more advanced use-cases like leaderboards. The API is\ndocumented at `http://localhost:3030/docs`.\n\n## Gamification Configuration\n\nThe gamification rules must be configured within the `config/gamification.yml`\nfile. This file is parsed on application start and must be adjusted to your\ngamification use-case. The most basic configuration looks like this:\n\n```yml\nXPs: []\nlevels:\n  type: linear\n  interval: 100\nevents: {}\nachievements: {}\n```\n\nDetailed configuration options follow. Please note that there currently is no\nvalidation logic in place to check for errors. The application will likely\ncrash if you make an error in your configuration!\n\n### XP-Pools\n\nDefine the different xp-pools there are within your application.\nThe \"XP\" xp-pool is always defined.\n\n```yml\nXPs:\n  - money\n  - myOtherXP\n  - myXP\n```\n\nNote: The xp-pool names defined here are not currently validated by the\nconfiguration parser, i.e. not specifying the available xp-pools or specifying\nadditional xp-pools does not raise an error currently. It is, however, planned\nto validate the configuration more thoroughly, which would then raise an error\nif you use an xp-pool not defined here.\n\n### Levels\n\nDefine when to increase a user's level based on their XP. The level is\nalways purely based on the user's current XP. Each user, regardless of this\nconfiguration, always starts at level 1. That means that the first\nconfiguration value specifies when the user reaches level 2, not level 1.\nThere are three ways to configure levels:\n\n```yml\nlevels:\n  # Manual level steps: Level 2 will be reached at 10 XP, level 3 at 100 XP\n  # and level 4 at 1000 XP.\n  type: manual\n  steps: [10,100,1000]\n  # Linear level steps: The level is increased for every 100 XP, i.e. level 2\n  # is reached at 100 XP, level 3 is reached at 200 XP, and so on.\n  type: linear\n  interval: 100\n  # Exponential level steps: The XP required to reach the next level doubles\n  # after every level. While the second level takes 100 XP, the next level will\n  # take 200 XP, then 400 XP, and so on.\n  type: exponential\n  starting_value: 100\n```\n\n### Events\n\nDefine which events to listen to. All other events are ignored by the service.\nEach key is an event name to listen to. The associated value describes\nimmediate actions to take when receiving the event.\n\nYou can use the `actions` array to specify actions to be executed as soon as the\nevent is received. \n\n```yml\nevents:\n  # In its simplest form, an event name\n  # can simply be followed by `: ~`, in which case no further immediate actions\n  # are executed when receiving the event.\n  MyEvent: ~\n  # This is equivalent to\n  MyEvent:\n    actions: []\n```\n\nEach action is an object with a single top-level key denoting the action type.\nCurrently, only the \"xp\" action is supported. Implicitly, the awardee is always\nassumed to be defined in the event's top-level `user_id` field. When you don't\nwant the user that triggers the event getting the xp, you can use the\n`awardee_id` configuration option to instead get the awardee's id from the\nevent's payload.\n\n```yml\nactions:\n  - xp:\n    # The name of the xp-pool\n    name: myXP\n    # The amount to add. You can also specify a negative amount here\n    # to subtract xp.\n    amount: 1\n    # Optional: The field inside the payload containing the awardee's user_id.\n    awardee_id: receiver_id\n```\n\n### Achievement Rules\n\n```yml\n# Define all available achievements. These definitions are called\n# \"achievement rules\". Each key is the unique name of an achievement. The\n# associated value describes further details related to this achievement.\nachievements:\n  # In its simplest, but useless form, an achievement looks like this:\n  MyAchievement: ~\n  # This is equivalent to the following configuration:\n  MyAchievement:\n    # List of requirements for the achievement.\n    requirements: []\n    # List of other achievement names to un-grant, should the user have them,\n    # when the achievement is granted.\n    replaces: []\n    maxAwarded: 1\n    maxAwardedTotal: 1\n    actions: []\n    hidden: false\n    # Not yet implemented: scope\n    scope: [user_id]\n```\n\n#### Achievement Rule `requirements`\n\nThe `requirements` key specifies all requirements that must be met for the\nachievement to be granted to the user. Each requirement consists a single\ntop-level key denoting the type of the requirement. The corresponding value sets\nfurther configuration options. By default, all requirements must be met for the\nachievement to be granted. In addition, to the requirements defined here, the\n`maxAwarded` and `maxAwardedTotal` options described below may also restrict\nwhether the achievement can be granted.\n\nThe supported four requirement types are detailed in the following sections.\nSome of them support the `amount` setting. The `amount` can either be a number\n`x`, which will be seen as `\u003e= x`, or one of the following strings:\n\n- `\"== x\"`: exactly `x`\n- `\"!= x\"`: not exactly `x`\n- `\"\u003e= x\"`: at least `x`\n- `\"\u003e x\"`: more than `x`\n- `\"\u003c= x\"`: at most `x`\n- `\"\u003c x\"`: less than `x`\n\n##### `xp` Achievement Rule `requirements`\n\nThe `xp` requirement allows you to specify the amount of xp from one of the\nxp-pools required for this achievement to be granted. You must specify the name\nof the xp-pool as well as the amount needed. The following example is\nfulfilled if the user has less than 42 XP:\n\n```yml\n- xp:\n    name: XP\n    amount: \"\u003c 42\"\n```\n\n##### `achievement` Achievement Rule `requirements`\n\nThe `achievement` requirement allows you to specify other achievements required\nfor this achievement to be granted. You must specify the name of the other\nachievement as well as the amount needed. The following example is fulfilled if\nthe user has two `MyAchievement` achievements:\n\n```yml\n- achievement:\n    name: MyAchievement\n    amount: 2\n```\n\n##### `AnyOf` Achievement Rule `requirements`\n\nThe `AnyOf` requirement allows you to specify an \"OR\" semantic between\nrequirements. The requirement's value is a list of child-requirements. The\nrequirement is fulfilled if any of the child-requirements is fulfilled. The\nfollowing example is fulfilled if the user has at least 10 yourXP or at least 20\nXP:\n\n```yml\n- AnyOf:\n  - xp:\n    name: yourXP\n    amount: 10\n  - xp:\n    name: XP\n    amount: 20\n```\n\n##### `event` Achievement Rule `requirements`\n\nThe `event` requirement allows you to specify events that must have been\nreceived in order for this achievement to be granted. You must specify the name\nof the event as well as the amount needed. You can optionally also define\n\"conditions\" on the received event. Here's a complex example showing the\nbehaviour:\n\n```yml\n- event:\n  name: YourEventName\n  amount: \u003e= 10\n  conditions:\n    # Conditions can have two types: `parameter` and `AnyOf`. As with\n    # requirements, conditions are using the AND semantic.\n    - parameter: someParameter\n      value: 100\n    - AnyOf:\n      - parameter: someOtherParameter\n        value: 10\n      - parameter: someOtherParameter2\n        value: 100\n      - AnyOf:\n        - parameter: someOtherParameter3\n          value: 10\n        - parameter: someOtherParameter4\n          value: 100\n```\n\n#### Achievement Rule `replaces`\n\nThe `replaces` key allows you to specify an array of other achievement names.\nShould the user receiving the new achievement have one of the other achievements\nspecified in here, they are taken away from the user. This is useful for\nmulti-stage achievements where the best \"gold\" achievement replaces the \"silver\"\nachievement. If the user has more than one of the replaced achievements,\nthey are all removed.\n\n#### Achievement Rule `maxAwarded` and `maxAwardedTotal`\n\nThese two settings configure how often the achievement can be granted to a user.\n`maxAwarded` specifies how often a user can have the achievement at the same\ntime. `maxAwardedTotal` specifies how often a user can be granted the\nachievement over time. Both of them are optional, however, if both are given,\nmake sure that `maxAwarded` is less or equal than `maxAwardedTotal`. If both\n`maxAwarded` and `maxAwardedTotal` are not set, they default to 1. If only\n`maxAwarded` is set, `maxAwardedTotal` defaults to positive infinity. If only\n`maxAwardedTotal` is set, `maxAwarded` defaults to `maxAwardedTotal`.\n\n#### Achievement Rule `actions`\n\nYou can use the `actions` array to specify actions to be executed whenever the\nachievement is granted. Each action is an object with a single top-level key\ndenoting the action type. Currently, only the \"xp\" action is supported:\n\n```yml\nactions:\n  - xp:\n    # The name of the xp-pool\n    name: myXP\n    # The amount to add. You can also specify a negative amount here\n    # to subtract xp.\n    amount: 1\n```\n\n#### Achievement Rule `hidden`\n\nThis boolean describes whether or not the achievement is \"public\" and returned\nwhen the /user endpoint is used. It defaults to `false`. This is useful for\nachievements which are only used to represent state and should not be visible to\nthe user.\n\n#### Achievement Rule `scope`\n\nThis is not yet implemented. It is thought to cover cases where the achievement\nis granted in the scope of an entity, for example a course or classroom.\n\n## Internals\n\nThis project uses [Feathers](http://feathersjs.com) as API framework and MongoDB\nfor storing data. Mongoose is used to validate the stored data.\n\n### Feathers services\n\nThe gamification service is built using four Feathers services:\n\n1. Achievements: The achievements service handles achievement data and stores\ngranted achievements in the `achivements` collection. Each row consists of the\n`user_id`, the `name` (achievement name), `current_amount` (how often the user\ncurrently has the achievement), `total_amount` (how often the user has received\nthe achievement) and `scope` (not yet implemented).\n\n2. Events: The events service handles event data and stores all incoming events\nin the `events` collection. Each row consists of the `user_id`, the `name`\n(event name), and the `payload` (event payload).\n\n3. XP: The xp-pool service handles xp-pool data and stores all granted xp in the\n`xp` collection. Each row consists of the `user_id`, the `name` (xp-pool\nname), and the `amount` (amount of xp the user has in this xp-pool).\n\n4. User: The user service aggregates data from the other three services. It can\nbe used to retrieve gamification information for a single user. It is the only\nservice without its own collection. It is also responsible for calculating the\nuser's level.\n\n## Development\n\n### Local Development\n\n1. Make sure you have [NodeJS](https://nodejs.org/) \u003e= 8.0.0, [npm](https://www.npmjs.com/) \u003e= 6.1.0 and MongoDB installed.\n2. If you don't have a local RabbitMQ installation, temporarily remove the `AmqpConnector.connect()` call from `src/index.js`.\n3. Install npm dependencies\n\n    ```\n    cd path/to/gamification; npm install\n    ```\n\n4. Start the app\n\n    ```\n    npm dev\n    ```\n\n### Docker Development\n\nThis project provides two `docker-compose` files, one meant for development and\none meant for production. The production `docker-compose.yml` configuration does\nnot contain definitions for the RabbitMQ service, because the gamification\nservice is thought to connect to an already existing RabbitMQ instance. The\ndevelopment `docker-compose.dev.yml` file, however, also contains a simple\nRabbitMQ definition to make development easier.\n\nFirst run `npm install`. Then start the docker environment.\n\n```\ndocker-compose -f docker-compose.dev.yml up\n```\n\nThis starts the containers for the app, MongoDB and RabbitMQ.\nThe app is then available at http://localhost:3030/.\n\n### RabbitMQ: Sending events manually\n\nThe RabbitMQ management interface is available at http://localhost:15672. In development mode, use Username `guest` and Password `guest` to login.\n\nYou can send events manually in the *Exchanges* section. Select the exchange and then publish your message at *Publish message*. Don't forget to insert the *routing key*.\n\n### Testing\n\nRun `npm test` to run all linters and tests. Use `npm coverage` to also generate\ncoverage.\n\n## Running in Production\n\nIt is recommended to use `docker-compose` to run this microservice in\nproduction. Execute `RABBITMQ_HOST=rabbit-host:5672 docker-compose up` in the\nproject's root directory to start the Node.js app and a MongoDB container. It is\nyour responsibility to start a separate RabbitMQ container and set the\n`RABBITMQ_HOST` environment variable respectively. If you don't want to use the\nRabbitMQ integration, simply omit the environment variable.\n\n## License\n\nCopyright (c) 2018 Kim-Pascal Borchart, Christian Flach, Corinna Jaschek,\nSebastian Kliem, Mandy Klingbeil, Marcus Konrad, Frederike Ramin.\n\nLicensed under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhpi-schul-cloud%2Fgamification","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhpi-schul-cloud%2Fgamification","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhpi-schul-cloud%2Fgamification/lists"}