{"id":23402884,"url":"https://github.com/kieranmueller/twitter-api","last_synced_at":"2026-05-18T19:33:32.325Z","repository":{"id":179533161,"uuid":"663645974","full_name":"KieranMueller/twitter-api","owner":"KieranMueller","description":"Java Spring Boot Twitter REST API","archived":false,"fork":false,"pushed_at":"2023-07-07T19:33:43.000Z","size":263,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-08T22:28:40.341Z","etag":null,"topics":["api","java","rest","spring","twitter"],"latest_commit_sha":null,"homepage":"","language":"Java","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/KieranMueller.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-07T19:31:06.000Z","updated_at":"2023-08-07T17:34:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"15b3758c-4ac5-4815-9a0d-e0252a23dac4","html_url":"https://github.com/KieranMueller/twitter-api","commit_stats":null,"previous_names":["kieranmueller/twitter-api"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/KieranMueller/twitter-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KieranMueller%2Ftwitter-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KieranMueller%2Ftwitter-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KieranMueller%2Ftwitter-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KieranMueller%2Ftwitter-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KieranMueller","download_url":"https://codeload.github.com/KieranMueller/twitter-api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KieranMueller%2Ftwitter-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274447533,"owners_count":25287114,"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","status":"online","status_checked_at":"2025-09-10T02:00:12.551Z","response_time":83,"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":["api","java","rest","spring","twitter"],"created_at":"2024-12-22T12:37:50.652Z","updated_at":"2026-05-18T19:33:27.304Z","avatar_url":"https://github.com/KieranMueller.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Assessment 1\n===============================\n\n## Overview\n\nFor this assessment, you are tasked with implementing a RESTful API using Spring Boot, JPA, and Postgresql. Specifically, you will be implementing an API that exposes operations for social media data that resembles the conceptual model of Twitter.\n\nYou will implement this API from scratch, working from a series of endpoint specifications (found at the end of this document) to develop a mental model of the data. You will develop a suitable database schema and write Spring services and controllers to handle requests, perform validation and business logic, and to transform data between the API and database models.\n\n## Testing the API\nIncluded in this skeleton are 2 json files required to run the test suite for this final project. To run the tests you will need postman's newman CLI. To install newman run the command `npm install -g newman`. Once newman is installed you need to navigate to the folder containing the Assessment 1 Test Suite \u0026 Assessment 1 environment json files. Once there you can run the command `newman run \"Assessment 1 Test Suite.postman_collection.json\" -e \"Assessment 1.postman_environment.json\"`. When all tests are passing successfully you will pass 330 assertions and should see something similar to the following in your terminal:\n\n\u003cimg width=\"458\" alt=\"successful_tests\" src=\"https://user-images.githubusercontent.com/12191780/222555974-53992ad3-155c-4e77-9205-bc3b908e093c.png\"\u003e\n\n\n## Reading these Requirements\n\n### RESTful Endpoint Methods and URLs\nEach endpoint you are required to implement is documented by the REST method and URL used to access it. For example, an endpoint used to access the list of dogs maintained by a server might be described like:\n\n`GET  dogs`\n\nThis tells us that the endpoint requires the `GET` HTTP method and is located at the `dogs` url, i.e. at `http://host:port/dogs`.\n\n#### URL Variables\nSome endpoints have variables in their urls, and these are represented by a variable name surrounded by curly braces. For example, an endpoint that returns a breed of dog by name might be described by the following syntax:\n\n`GET breeds/{breedName}`\n\nThis tells us that the endpoint captures the path segment following `breeds/` with the variable `breedName`.\n\nRemember that the curly braces themselves are not part of the url, but anything outside of them is.\n\n#### Trailing Slashes\nThe endpoint specifications never supply a trailing slash, but they are allowed. It is up to you to decide whether you prefer trailing slashes for API endpoint URLs or not, but whichever you choose, be consistent from endpoint to endpoint.\n\n### Types and Object Properties\nThe syntax used to describe the request and response bodies for each required api endpoint is a variation of javascript's object literal syntax, in order to promote legibility, but the endpoints themselves should use JSON to represent data.\n\nObject literals are used to describe the shape of each data model, and property values are used to describe the property's data type. For example, a `Dog` data type might be described by the following syntax:\n```javascript\n{ // Dog\n  name: 'string',\n  age: 'integer'\n}\n```\nThis tells us that a dog has two properties, `name` and `age`, and that they should be a `string` and `integer`, respectively.\n\n#### Optional properties\nSome properties are optional, meaning that they can be represented by `undefined` in javascript or `null` in java or sql. This is represented by giving a `?` suffix to the property name. For example, a `Dog` type like the one defined before could have an optional nickname, which might be described by the following syntax:\n```javascript\n{ // Dog\n  name: 'string',\n  nickname?: 'string',\n  age: 'integer'\n}\n```\nThis tells us that a `Dog` has a property `nickname` that may be 'string' or may not be present at all.\n\nAny properties without a `?` suffix should be considered required.\n\nKeep in mind this is not valid javascript or JSON syntax, and that the `?` is not part of the property name.\n\n#### Built-in Types\nSome types, like `'string'` and '`integer`', mean exactly what you would expect them to - they refer to simple types common to both Java and JSON. Others, though, are less obvious, and some require different representations in Java, JSON, and SQL.\n\nTo ensure consistency, here is a quick overview of some of the common types used in this specification.\n\n- `'string'` refers to a string of unicode characters, and can be represented by the `String` types in all relevant languages\n- `'integer'` refers to a 32-bit signed integer, and can be represented by the `number` type in JSON and the `Integer` type in Java.\n- `'timestamp'` refers to a UNIX timestamp, i.e. the number of milliseconds since the beginning of the UNIX epoch - January 1, 1970. In JSON, this should be represented as a number, specifically a `long`. On the server side, as well as in the database, this should be represented as an instance of `java.sql.Timestamp`.\n\n#### Custom Types\nProperty types can also refer to types defined in this specification. For example, an owner for the `Dog` type defined above might be described by the following syntax:\n```javascript\n{ // Owner\n  dog: 'Dog'\n}\n```\nThis tells us that an `Owner` has a property `dog` that is described by the `Dog` type, defined elsewhere in the specification.\n\n#### Anonymous Types\nSometimes a type is never reused in the specification. In those cases, an object literal can be used to describe the type without naming it. For example, a `ChewToy` data type might be described by the following syntax:\n```javascript\n{ // ChewToy\n  material: 'string',\n  color: 'string',\n  dimensions: {\n    width: 'integer',\n    height: 'integer'\n  }\n}\n```\nHere we could have defined a `Dimensions` type with the following syntax:\n```javascript\n{ // Dimensions\n  width: 'integer',\n  height: 'integer'\n}\n```\nBut if `ChewToy` is the only type that makes use of `Dimensions`, it's easier to define `Dimensions` as an anonymous type.\n\n#### Array Types\nIf a property should be an array of a specific type of element, it is represented as an array literal with the element type as a string inside the array. For example, a kennel with a list of dogs might be described by the following syntax:\n```javascript\n{ // Kennel\n  dogs: ['Dog']\n}\n```\nThis tells us that a `Kennel` has a property `dogs` that is an array of elements, the type of each of which is described by the `Dog` type\n\n## Entity Relationship Diagram\n![Spring Assessment ERD](https://user-images.githubusercontent.com/12191780/187276918-ccb2d373-be3b-42ff-a74d-5560ba806a10.png)\n\n\nThis ERD represents the database that students will create for this project. Students should only create three classes, `User`, `Tweet`, and `Hashtag`, annotated with `@Entity`. There are, however, two additional classes that students will need to create for this project: `Credentials` and `Profile`. These two classes will be annotated with `@Embeddable` and will be used inside of the `User` entity class with the `@Embedded` annotation. This allows us to maintain credentials and profile as seperate objects in Java while still being stored in just one table in the database.\n\n**IMPORTANT:** The `User` entity will also need to use an `@Table(name=\u003cnewName\u003e)` annotation to give its table a different name as `user` is a reserved keyword in PostgreSQL.\n\n## API Data Types\nThe semantics of the operations exposed by the API endpoints themselves are discussed in the following section, but in this section, the API data model is defined and the conceptual model for the application is explained in some depth. Additionally, some hints and constraints for the database model are discussed here.\n\nIn general, the data types defined here are in their general, read-only forms. That means that these are the versions of the models that are returned by `GET` operations or nested inside other objects as auxiliary data. Most `POST` operations, which often create new records in the database, require specialized versions of these models. Those special cases are covered by the endpoint specifications themselves unless otherwise noted.\n\n### User\nA user of the application. The `username` must be unique. The `joined` timestamp should be assigned when the user is first created, and must never be updated.\n```javascript\n{ // User\n  username: 'string',\n  profile: 'Profile',\n  joined: 'timestamp'\n}\n```\n\n### Profile\nA user's profile information. Only the `email` property is required.\n```javascript\n{ // Profile\n  firstName?: 'string',\n  lastName?: 'string',\n  email: 'string',\n  phone?: 'string'\n}\n```\n\n### Credentials\nA user's credentials. These are mostly used for validation and authentication during operations specific to a user. Passwords are plain text for the sake of academic simplicity, and it should be kept in mind that this is never appropriate in the real world.\n```javascript\n{ // Credentials\n  username: 'string',\n  password: 'string'\n}\n```\n\n### Hashtag\nA hashtag associated with tweets that contain its label. The `label` property must be unique, but is case-insensitive. The `firstUsed` timestamp should be assigned on creation, and must never be updated. The `lastUsed` timestamp should be updated every time a new tweet is tagged with the hashtag.\n```javascript\n{ // Hashtag\n  label: 'string',\n  firstUsed: 'timestamp',\n  lastUsed: 'timestamp'\n}\n```\n\n## Tweet\nA tweet posted by a user. The `posted` timestamp should be assigned when the tweet is first created, and must not be updated.\n\nThere are three distinct variations of tweets: simple, repost, and reply.\n- A simple tweet has a `content` value but no `inReplyTo` or `repostOf` values\n- A repost has a `repostOf` value but no `content` or `inReplyTo` values\n- A reply has `content` and `inReplyTo` values, but no `repostOf` value\n\n```javascript\n{ // Tweet\n  id: 'integer'\n  author: 'User',\n  posted: 'timestamp',\n  content?: 'string',\n  inReplyTo?: 'Tweet',\n  repostOf?: 'Tweet'\n}\n```\n\n### Context\nThe reply context of a tweet. The `before` property represents the chain of replies that led to the `target` tweet, and the `after` property represents the chain of replies that followed the `target` tweet.\n\nThe chains should be in chronological order, and the `after` chain should include all replies of replies, meaning that all branches of replies must be flattened into a single chronological list to fully satisfy the requirements.\n```javascript\n{ // Context\n  target: 'Tweet',\n  before: ['Tweet'],\n  after: ['Tweet']\n}\n```\n\n## API Endpoints\n\n### `GET   validate/tag/exists/{label}`\nChecks whether or not a given hashtag exists.\n\n#### Response\n```javascript\n'boolean'\n```\n\n### `GET   validate/username/exists/@{username}`\nChecks whether or not a given username exists.\n\n#### Response\n```javascript\n'boolean'\n```\n\n### `GET   validate/username/available/@{username}`\nChecks whether or not a given username is available.\n\n#### Response\n```javascript\n'boolean'\n```\n\n### `GET     users`\nRetrieves all active (non-deleted) users as an array.\n\n#### Response\n```javascript\n['User']\n```\n\n### `POST    users`\nCreates a new user. If any required fields are missing or the `username` provided is already taken, an error should be sent in lieu of a response.\n\nIf the given credentials match a previously-deleted user, re-activate the deleted user instead of creating a new one.\n\n#### Request\n```javascript\n{\n  credentials: 'Credentials',\n  profile: 'Profile'\n}\n```\n\n#### Response\n```javascript\n'User'\n```\n\n### `GET     users/@{username}`\nRetrieves a user with the given username. If no such user exists or is deleted, an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n'User'\n```\n\n\n### `PATCH   users/@{username}`\nUpdates the profile of a user with the given username. If no such user exists, the user is deleted, or the provided credentials do not match the user, an error should be sent in lieu of a response. In the case of a successful update, the returned user should contain the updated data.\n\n#### Request\n```javascript\n{\n  credentials: 'Credentials',\n  profile: 'Profile'\n}\n```\n\n#### Response\n```javascript\n'User'\n```\n\n### `DELETE  users/@{username}`\n\"Deletes\" a user with the given username. If no such user exists or the provided credentials do not match the user, an error should be sent in lieu of a response. If a user is successfully \"deleted\", the response should contain the user data prior to deletion.\n\n**IMPORTANT:** This action should not actually drop any records from the database! Instead, develop a way to keep track of \"deleted\" users so that if a user is re-activated, all of their tweets and information are restored.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n#### Response\n```javascript\n'User'\n```\n\n### `POST    users/@{username}/follow`\nSubscribes the user whose credentials are provided by the request body to the user whose username is given in the url. If there is already a following relationship between the two users, no such followable user exists (deleted or never created), or the credentials provided do not match an active user in the database, an error should be sent as a response. If successful, no data is sent.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n### `POST    users/@{username}/unfollow`\nUnsubscribes the user whose credentials are provided by the request body from the user whose username is given in the url. If there is no preexisting following relationship between the two users, no such followable user exists (deleted or never created), or the credentials provided do not match an active user in the database, an error should be sent as a response. If successful, no data is sent.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n### `GET     users/@{username}/feed`\nRetrieves all (non-deleted) tweets authored by the user with the given username, as well as all (non-deleted) tweets authored by users the given user is following. This includes simple tweets, reposts, and replies. The tweets should appear in reverse-chronological order. If no active user with that username exists (deleted or never created), an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     users/@{username}/tweets`\nRetrieves all (non-deleted) tweets authored by the user with the given username. This includes simple tweets, reposts, and replies. The tweets should appear in reverse-chronological order. If no active user with that username exists (deleted or never created), an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     users/@{username}/mentions`\nRetrieves all (non-deleted) tweets in which the user with the given username is mentioned. The tweets should appear in reverse-chronological order. If no active user with that username exists, an error should be sent in lieu of a response.\n\nA user is considered \"mentioned\" by a tweet if the tweet has `content` and the user's username appears in that content following a `@`.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     users/@{username}/followers`\nRetrieves the followers of the user with the given username. Only active users should be included in the response. If no active user with the given username exists, an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n['User']\n```\n\n### `GET     users/@{username}/following`\nRetrieves the users followed by the user with the given username. Only active users should be included in the response. If no active user with the given username exists, an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n['User']\n```\n\n### `GET     tags`\nRetrieves all hashtags tracked by the database.\n\n#### Response\n```javascript\n['Hashtag']\n```\n\n### `GET     tags/{label}`\nRetrieves all (non-deleted) tweets tagged with the given hashtag label. The tweets should appear in reverse-chronological order. If no hashtag with the given label exists, an error should be sent in lieu of a response.\n\nA tweet is considered \"tagged\" by a hashtag if the tweet has `content` and the hashtag's label appears in that content following a `#`\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     tweets`\nRetrieves all (non-deleted) tweets. The tweets should appear in reverse-chronological order.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `POST    tweets`\nCreates a new simple tweet, with the author set to the user identified by the credentials in the request body. If the given credentials do not match an active user in the database, an error should be sent in lieu of a response.\n\nThe response should contain the newly-created tweet.\n\nBecause this always creates a simple tweet, it must have a `content` property and may not have `inReplyTo` or `repostOf` properties.\n\n**IMPORTANT:** when a tweet with `content` is created, the server must process the tweet's content for `@{username}` mentions and `#{hashtag}` tags. There is no way to create hashtags or create mentions from the API, so this must be handled automatically!\n\n#### Request\n```javascript\n{\n  content: 'string',\n  credentials: 'Credentials'\n}\n```\n\n#### Response\n```javascript\n'Tweet'\n```\n\n### `GET     tweets/{id}`\nRetrieves a tweet with a given id. If no such tweet exists, or the given tweet is deleted, an error should be sent in lieu of a response.\n\n#### Response\n```javascript\n'Tweet'\n```\n\n### `DELETE  tweets/{id}`\n\"Deletes\" the tweet with the given id. If no such tweet exists or the provided credentials do not match author of the tweet, an error should be sent in lieu of a response. If a tweet is successfully \"deleted\", the response should contain the tweet data prior to deletion.\n\n**IMPORTANT:** This action should not actually drop any records from the database! Instead, develop a way to keep track of \"deleted\" tweets so that even if a tweet is deleted, data with relationships to it (like replies and reposts) are still intact.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n#### Response\n```javascript\n'Tweet'\n```\n\n### `POST    tweets/{id}/like`\nCreates a \"like\" relationship between the tweet with the given id and the user whose credentials are provided by the request body. If the tweet is deleted or otherwise doesn't exist, or if the given credentials do not match an active user in the database, an error should be sent. Following successful completion of the operation, no response body is sent.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n### `POST    tweets/{id}/reply`\nCreates a reply tweet to the tweet with the given id. The author of the newly-created tweet should match the credentials provided by the request body. If the given tweet is deleted or otherwise doesn't exist, or if the given credentials do not match an active user in the database, an error should be sent in lieu of a response.\n\nBecause this creates a reply tweet, content is not optional. Additionally, notice that the `inReplyTo` property is not provided by the request. The server must create that relationship.\n\nThe response should contain the newly-created tweet.\n\n**IMPORTANT:** when a tweet with `content` is created, the server must process the tweet's content for `@{username}` mentions and `#{hashtag}` tags. There is no way to create hashtags or create mentions from the API, so this must be handled automatically!\n\n#### Request\n```javascript\n{\n  content: 'string',\n  credentials: 'Credentials'\n}\n```\n\n#### Response\n```javascript\n'Tweet'\n```\n\n### `POST    tweets/{id}/repost`\nCreates a repost of the tweet with the given id. The author of the repost should match the credentials provided in the request body. If the given tweet is deleted or otherwise doesn't exist, or the given credentials do not match an active user in the database, an error should be sent in lieu of a response.\n\nBecause this creates a repost tweet, content is not allowed. Additionally, notice that the `repostOf` property is not provided by the request. The server must create that relationship.\n\nThe response should contain the newly-created tweet.\n\n#### Request\n```javascript\n'Credentials'\n```\n\n#### Response\n```javascript\n'Tweet'\n```\n\n### `GET     tweets/{id}/tags`\nRetrieves the tags associated with the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\n**IMPORTANT** Remember that tags and mentions must be parsed by the server!\n\n#### Response\n```javascript\n['Hashtag']\n```\n\n### `GET     tweets/{id}/likes`\nRetrieves the active users who have liked the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\nDeleted users should be excluded from the response.\n\n#### Response\n```javascript\n['User']\n```\n\n### `GET     tweets/{id}/context`\nRetrieves the context of the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\n**IMPORTANT:** While deleted tweets should not be included in the `before` and `after` properties of the result, transitive replies should. What that means is that if a reply to the target of the context is deleted, but there's another reply to the deleted reply, the deleted reply should be excluded but the other reply should remain.\n\n#### Response\n```javascript\n'Context'\n```\n\n### `GET     tweets/{id}/replies`\nRetrieves the direct replies to the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\nDeleted replies to the tweet should be excluded from the response.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     tweets/{id}/reposts`\nRetrieves the direct reposts of the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\nDeleted reposts of the tweet should be excluded from the response.\n\n#### Response\n```javascript\n['Tweet']\n```\n\n### `GET     tweets/{id}/mentions`\nRetrieves the users mentioned in the tweet with the given id. If that tweet is deleted or otherwise doesn't exist, an error should be sent in lieu of a response.\n\nDeleted users should be excluded from the response.\n\n**IMPORTANT** Remember that tags and mentions must be parsed by the server!\n\n#### Response\n```javascript\n['User']\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkieranmueller%2Ftwitter-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkieranmueller%2Ftwitter-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkieranmueller%2Ftwitter-api/lists"}