{"id":13507877,"url":"https://github.com/erneestoc/feedx","last_synced_at":"2026-04-04T16:14:17.277Z","repository":{"id":186200590,"uuid":"127092221","full_name":"erneestoc/feedx","owner":"erneestoc","description":"Generic feed adding social features to current applications. ","archived":false,"fork":false,"pushed_at":"2018-04-30T00:49:45.000Z","size":235,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T08:11:29.211Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/erneestoc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-03-28T06:10:08.000Z","updated_at":"2023-09-01T10:49:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"cbf3edab-b654-49c9-9c8a-2ef0b5a1783d","html_url":"https://github.com/erneestoc/feedx","commit_stats":null,"previous_names":["erneestoc/feedx"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erneestoc%2Ffeedx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erneestoc%2Ffeedx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erneestoc%2Ffeedx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/erneestoc%2Ffeedx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/erneestoc","download_url":"https://codeload.github.com/erneestoc/feedx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246301963,"owners_count":20755512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-08-01T02:00:41.869Z","updated_at":"2026-04-04T16:14:17.244Z","avatar_url":"https://github.com/erneestoc.png","language":"Elixir","funding_links":[],"categories":["Examples and funny stuff"],"sub_categories":[],"readme":"# Feedx\n\n[![Build Status](https://travis-ci.org/erneestoc/feedx.svg?branch=master)](https://travis-ci.org/erneestoc/feedx)\n[![Coverage Status](https://coveralls.io/repos/github/erneestoc/feedx/badge.svg?branch=master)](https://coveralls.io/github/erneestoc/feedx?branch=master)\n\n\nGeneric feed adding social features to current applications.\n\n---\n\n* [Details](#details)\n* [Design](#design)\n* [Usage](#usage)\n* [Setup](#setup)\n\n---\n\n## Details\n\nRabbitMQ as event producer. This app consumes, stores, and serves. The goal is to create a plug-n-play social feed for existing applications. May be easy to consume events from other sources.\n\n## Design\n\nThis is an OTP umbrella application, containing other 3 OTP applications within the `apps` folder.\n\n```\n/apps/bus\n/apps/store\n/apps/web\n```\n\n### bus\n\n- `Bus` contains the RabbitMQ `Consumer` generic server, which responsibility is to store events into the feed.\n\n### store\n\n- `FeedRepo` is the main data store for our feed. Can be backed by any [ecto's supported databases](https://github.com/elixir-ecto/ecto#usage).\n- `SourceRepo` contains a connection to other app database, in which user's are stored. We should be able to get user name, id, and profile pic from one table. The store app implements a simple caching using [con_cache](https://github.com/sasa1977/con_cache), so that we don't hit the `Source` database too much. The caching mecanisms and access to the database can be [configured](#setup), if we don't setup a TTL, each user will only be looked up once per app restart. (We can avoid apps restart using erlang's code hot swap 🙂)\n- `Feed` gen_server is the main API for consuming the feed.\n- `FeedBuilder` gen_server is an API for building up the feed.\n- `Users` gen_server retrieves and caches users given an id. Mostly accessed by the feed server.\n- `Comments` gen_server is an API for showing, creating, updating, and deleting comments.\n- `Likes` gen_server is an API for liking and unliking feed events.\n\n### web\n\n- The web project contains a json API and websockets for consuming and updating the feed. Uses [phoenix framework](http://phoenixframework.org/)\n\n## Usage\n\nAn app should post events to RabbitMQ with a given format, in order for this app to consume and process events to build the feed. We can always shutdown AMQP, and feed the `Store.FeedBuilder` using [distributed erlang](http://erlang.org/doc/reference_manual/distributed.html), the API, or building some other mecanism.\n\nWe support 3 kind of events. `create`, `update`, `delete`. In order for us to handle an `update` or `delete` correctly, we should have already processed the create. `udpate` wont work once the event is deleted.\n\n### Our generic event looks like this.\n\n```\n{\n\t\"event\": {\n\t\t\"type\": \"create\",\n\t\t\"tenant\": 0,\n\t\t\"user_id\": 0,\n\t\t\"content\": \"Main data content\",\n\t\t\"extra\": {},\n\t\t\"date\": \"ISOz date\"\n\t}\n}\n```\n\n### This app exposes an API endpoint.\n\n__GET @ /api/v1/feed__\n\n```\n{\n\tdata: [{\n\t\t\"id\": 1,\n\t\t\"user\": {\n\t\t\t\"id\": 1,\n\t\t\t\"full_name\": \"Nombre\",\n\t\t\t\"profile_pic\": \"profile\"\n\t\t},\n\t\t\"type\": \"post\",\n\t\t\"content\": \"some content\",\n\t\t\"comments\": {\n\t\t\t\"total\": 10,\n\t\t\t\"preview\": [{\n\t\t\t\t\"user\":...,\n\t\t\t\t\"comment\": \"hello world!\"\n\t\t\t}]\n\t\t}\n\t}]\n}\n```\n\n__GET @ /api/v1/feed/:tenant_id__\n\n```\n{\n\tdata: [{\n\t\t\"id\": 1,\n\t\t\"user\": {\n\t\t\t\"id\": 1,\n\t\t\t\"full_name\": \"Nombre\",\n\t\t\t\"profile_pic\": \"profile\"\n\t\t},\n\t\t\"type\": \"post\",\n\t\t\"content\": \"some content\",\n\t\t\"comments\": {\n\t\t\t\"total\": 10,\n\t\t\t\"preview\": [{\n\t\t\t\t\"user\":...,\n\t\t\t\t\"comment\": \"hello world!\",\n\t\t\t\t\"id\": 1\n\t\t\t}]\n\t\t}\n\t}]\n}\n```\n\n__GET @ /api/v1/feed/:tenant_id/event/:event_id/comments__\n\n```\n{\n\tdata: [{\n\t\t\"user\": ...,\n\t\t\"comment\": \"Content\",\n\t\t\"id\": 1\n\t}]\n}\n```\n\n__POST @ /api/v1/feed/:tenant_id/event/:event_id/comments__\n\n```\n{\n\tcontent: \"Hello world!\"\n}\n```\n\n__DELETE @ /api/v1/feed/:tenant_id/event/:event_id/comments/:comment_id__\n\n__GET @ /api/v1/feed/:tenant_id/event/:event_id/like__\n\n__GET @ /api/v1/feed/:tenant_id/event/:event_id/unlike__\n\n### Websockets\n\nWe can also subscibe for updates using [phoenix channels](https://hexdocs.pm/phoenix/channels.html).\n\nWebsocket @ `/ws/v1/feed`\n\n### Channel Subscriptions\n\nSubscribe for company events @ `company:\u003cid\u003e`\n```\n// new event\n{\n\t\"type\": \"create\",\n\t\"data\": {},\n\t\"id\": 0\n}\n\n// update event\n{\n\t\"type\": \"create\",\n\t\"data\": {},\n\t\"id\": 0\n}\n\n// delete event\n{\n\t\"id\": 0\n}\n```\n\nSubscribe for event changes @ `event:\u003cid\u003e`\n\n```\n// new comment\n{\n\t\"type\": \"create_comment\",\n\t\"data\": {},\n\t\"id\": 0\n}\n\n// update comment\n{\n\t\"type\": \"update_comment\",\n\t\"data\": {},\n\t\"id\": 0\n}\n\n// delete comment\n{\n\t\"type\": \"delete_comment\",\n\t\"id\": 0\n}\n\n// new like state\n{\n\t\"type\": \"update_likes\",\n\t\"data\": {}\n}\n\n// someone is typing\n{\n\t\"type\": \"typing\",\n\t\"data\": {}\n}\n```\n\n\n## setup\n\n\nChange RabbitMQ config on `apps/bus/config/{dev, test, prod}.exs`.\n\n```\nconfig :bus, :rabbitmq, \"amqp://guest:guest@127.0.0.1\"\n```\n\nChange feed data store settings on `apps/store/config/{dev, test, prod}.exs`\n\n```\nconfig :store, Store.FeedRepo,\n  adapter: Ecto.Adapters.Postgres,\n  username: \"postgres\",\n  password: \"postgres\",\n  database: \"store_db_dev\",\n  hostname: \"localhost\",\n  pool_size: 10\n```\n\nSetup your app's database connection, and the name of the table and columns to pull the users parameters.\n\n```\nconfig :store, Store.SourceRepo,\n  adapter: Ecto.Adapters.Postgres,\n  username: \"postgres\",\n  password: \"postgres\",\n  database: \"store_db_dev\",\n  hostname: \"localhost\",\n  pool_size: 10\n\nconfig :store, :external_db_table_name, \"tabla\"\nconfig :store, :external_db_full_name, :full_name\nconfig :store, :external_db_user_id, :id\nconfig :store, :external_db_profile_pic, :image_url\n```\n\nCache mecanism for users and other feed interactions can be customized. TTL is not active by default.\n\n```\nconfig :store, :user_cache,\n  ttl: true,\n  touch_on_read: true,\n  global_ttl: 20,\n  check_interval: 2\n\nconfig :store, :interactions_cache,\n  ttl: false\n```\n\n## TODO\n\n- Better docs\n- More tests\n- Generic authentication and authorization mecanism\n- Explore other ways of retrieving user's data.\n- Explore other ways of storing data. [Cassandra](https://github.com/blitzstudios/triton), [Riak](http://basho.com/products/), [Mnesia](http://erlang.org/doc/man/mnesia.html) 🤔\n\n---\n\n[Easy deploy using edeliver](https://github.com/edeliver/edeliver)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferneestoc%2Ffeedx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ferneestoc%2Ffeedx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ferneestoc%2Ffeedx/lists"}