{"id":13558290,"url":"https://github.com/antirez/lamernews","last_synced_at":"2025-04-03T13:30:55.289Z","repository":{"id":1016550,"uuid":"2581086","full_name":"antirez/lamernews","owner":"antirez","description":"Lamer News -- an HN style social news site written in Ruby/Sinatra/Redis/JQuery","archived":false,"fork":false,"pushed_at":"2022-05-27T17:53:02.000Z","size":330,"stargazers_count":1354,"open_issues_count":47,"forks_count":200,"subscribers_count":46,"default_branch":"master","last_synced_at":"2024-05-01T23:52:30.448Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://lamernews.com","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antirez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2011-10-15T09:26:51.000Z","updated_at":"2024-04-30T03:33:44.000Z","dependencies_parsed_at":"2022-08-16T11:50:17.994Z","dependency_job_id":null,"html_url":"https://github.com/antirez/lamernews","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2Flamernews","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2Flamernews/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2Flamernews/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2Flamernews/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antirez","download_url":"https://codeload.github.com/antirez/lamernews/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247009504,"owners_count":20868565,"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-01T12:04:51.845Z","updated_at":"2025-04-03T13:30:54.979Z","avatar_url":"https://github.com/antirez.png","language":"Ruby","funding_links":[],"categories":["Ruby","others","Happy Exploring 🤘"],"sub_categories":[],"readme":"About\n===\n\nLamer news is an implementation of a Reddit / Hacker News style news web site\nwritten using Ruby, Sinatra, Redis and jQuery.\n\nThe goal is to have a system that is very simple to understand and modify and\nthat is able to handle a very high load using a small virtual server, ensuring\nat the same time a very low latency user experience.\n\nThis project was created in order to run http://lamernews.com but is free for\neverybody to use, fork, and have fun with.\n\nWe believe it is also a good programming example for Redis as a sole DB of a\nnontrivial, real world, web application.\n\nInstallation\n===\n\nLamer news is a Ruby/Sinatra/Redis/jQuery application.\nYou need to install Redis and Ruby 1.9.2+ with the following gems:\n\n* redis 3.0 or greater\n* hiredis\n* sinatra\n* json\n* ruby-hmac\n* net/smtp\n* openssl (not needed but will speedup the authentication if available).\n\nPlease note that Redis uses port 6379 by default, so you should either change\nthe port number in the configuration file (which is set to use port 10000), or\nconfigure Redis to use a matching port.\n\nHow to contribute\n===\n\nI plan to hack on Lamer News in my free time as it is interesting to have\na non trivial open source example for Redis that is also an useful application.\nHowever contributions are welcomed. Just make sure to:\n\n* Keep it simple. No complex code, no extreme ruby programming. Ideally non ruby people should understand the code without much efforts.\n* Don't use templates, they suck.\n* If your code slows down significantly the page generation time it will not get merged.\n* Do everything you can to avoid depending on new ruby gems.\n* Open an issue on github before firing your editor to see if there are good chances that your changes will be merged.\n* If you don't want to follow all this rules, forking the code is *encouraged*! The license is two clause BSD, do with this code what you want. Run your site, turn it into a blog, hack it to the extreme consequences. Have fun :)\n\nWeb sites using this code\n===\n\n* http://lamernews.com Programming News.\n* http://hackingitalia.com Programming and Startup News (In Italian).\n* http://www.echojs.com JavaScript News.\n\nData Layout\n===\n\nUsers\n---\n\nEvery user is represented by the following fields:\n\nA Redis hash named `user:\u003cuser id\u003e` with the following fields:\n\n    id -\u003e user ID\n    username -\u003e The username\n    password -\u003e Hashed password, PBKDF2(salt|password) note: | means concatenation\n    ctime -\u003e Registration time (unix time)\n    karma -\u003e User karma, earned visiting the site and posting good stuff\n    about -\u003e Some optional info about the user\n    email -\u003e Optional, used to show gravatars\n    auth -\u003e authentication token\n    apisecret -\u003e api POST requests secret code, to prevent CSRF attacks.\n    flags -\u003e user flags. \"a\" enables administrative privileges.\n    karma_incr_time -\u003e last time karma was incremented\n    pwd_reset -\u003e unix time of the last password reset requested.\n    replies -\u003e number of unread replies\n\nAdditionally for every user there is the following key:\n\n    `username.to.id:\u003clowercase_username\u003e` -\u003e User ID\n\nThis is used to lookup users by name.\n\nFrequency of user posting is limited by a key named\n`user:\u003cuser_id\u003e:submitted_recently` with TTL 15 minutes. If a user\nattempts to post before that key has expired an error message notifies\nthe user of the amount of time until posting is permitted.\n\nAccount creation is rate limited by IP address with a key named\n`limit:create_user:\u003cip_address\u003e` with TTL 15 hours.\n\nAuthentication\n---\n\nUsers receive an authentication token after a valid pair of username/password\nis received.\nThis token is in the form of a SHA1-sized hex number.\nThe representation is a simple Redis key in the form:\n\n    `auth:\u003clowercase_token\u003e` -\u003e User ID\n\nNews\n---\n\nNews are represented as an hash with key name `news:\u003cnews id\u003e`.\nThe hash has the following fields:\n\n    id -\u003e News id\n    title -\u003e News title\n    url -\u003e News url\n    user_id =\u003e The User ID that posted the news\n    ctime -\u003e News creation time. Unix time.\n    score -\u003e News score. See source to check how this is computed.\n    rank -\u003e News score adjusted by age: RANK = SCORE / AGE^ALPHA\n    up -\u003e Counter with number of upvotes\n    down -\u003e Counter with number of downvotes\n    comments -\u003e number of comments\n\nNote: up, down, comments fields are also available in other ways but we\ndenormalize for speed.\n\nAlso recently posted urls have a key named `url:\u003cactual full url\u003e` with TTL 48\nhours and set to the news ID of a recently posted news having this url.\n\nSo if another user will try to post a given content again within 48 hours the\nsystem will simply redirect it to the previous news.\n\nNews is never deleted, but just marked as deleted adding the \"del\"\nfield with value 1 to the news object. However when the post is\nrendered into HTML, it is displayed as [deleted news] text.\n\nNews votes\n---\n\nEvery news has a sorted set with user upvotes and downvotes. The keys are named\nrespectively `news.up:\u003cnews id\u003e` and `news.down:\u003cnews id\u003e`.\n\nIn the sorted sets the score is the unix time of the vote creation, the element\nis the user ID of the voting user.\n\nPosting a news will automatically register an up vote from the user posting\nthe news.\n\nSaved news\n---\n\nThe system stores a list of upvoted news for every user using a sorted set named\n`user.saved:\u003cuser id\u003e`, index by unix time. The value of the sorted set elements\nis the `\u003cnews id\u003e`.\n\nSubmitted news\n---\n\nLike saved news every user has an associated sorted set with news he posted.\nThe key is called `user.posted:\u003cuser id\u003e`. Again the score is the unix time and\nthe element is the news id.\n\nTop and Latest news\n---\n\nnews.cron is used to generate the \"Latest News\" page.\nIt is a sorted set where the score is the Unix time the news was posted, and the\nvalue is the news ID.\n\nnews.top is used to generate the \"Top News\" page.\nIt is a sorted set where the score is the \"RANK\" of the news, and the value is\nthe news ID.\n\nComments\n---\n\nComments are represented using a very memory efficient pattern.\nThe system is implemented in the comments.rb file.\n\nIn short every thread (that is a collection of comments for a given\nnews) is represented by an hash. Every hash entry represents a\nsingle comment:\n\n* The hash field is the comment ID.\n* The hash value is a JSON representation of the \"comment object\".\n\nThe comment object has many fields, like `ctime` (creation time), `body`,\n`user_id`, and so forth. In order to render all the comments for a thread\nwe simply do an HGETALL to fetch everything. Then we run the list of\nreturned comments and build a graph of comments, calling a recursive\nfunction with this graph as input.\n\nComments are never deleted, but just marked as deleted adding the \"del\"\nfield with value 1 to the comment object. However when the thread is\nrendered into HTML deleted comments without childs are not displayed.\nDeleted comments with childs are displayed as [deleted comment] text.\n\nPlease check comments.rb for details, it is trivial to read.\n\nUser comments\n---\n\nAll the comments posted by a given user are also taken into a sorted set\nof comments, keyed by creation time. The key name is: `user.comments:\u003cuserid\u003e`.\n\nIn this sorted set the score is the unix time and the value is a string composed in this way: `\u003cnewsid\u003e-\u003ccommentid\u003e`. So a unique comment is referenced by the news id and the id of the comment inside the hash of comments for this news. Example of an actual comment pointer: `882-15`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantirez%2Flamernews","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantirez%2Flamernews","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantirez%2Flamernews/lists"}