Giter Site home page Giter Site logo

rpitv / glimpse-api Goto Github PK

View Code? Open in Web Editor NEW
3.0 5.0 0.0 3.63 MB

Backend API for the RPI TV Glimpse website

Home Page: https://rpi.tv

License: MIT License

Shell 0.01% Dockerfile 0.17% TypeScript 99.71% JavaScript 0.11%
rpi rpitv rensselaer rcos graphql hacktoberfest docker

glimpse-api's Introduction

Notice: This project has been relocated to the rpitv/glimpse monorepo


Logo of the project

Glimpse API · GitHub license

This is the backend API for Glimpse, the RPI TV website. For more information about the project, including features, check out the project wiki.

View the project in action at https://rpi.tv/.

Installing / Getting started

It's recommended to use Docker with Docker Compose for production deployments. To use the provided Docker Compose file, Docker Compose must be version 1.28.0 or higher.

Start by cloning this repository.

git clone https://github.com/rpitv/glimpse-api.git
cd glimpse-api

Then create your .env.docker file. You can use the .env.sample file as a template, and then modify it to match your environment.

cp .env.sample .env.docker

After that, you can start up the Docker containers.

docker compose --profile api-production up -d

This will pull the latest version of this repository's Docker image and start it up with the necessary dependencies. You can then use Prisma and the CLI to bootstrap your database. This requires the current LTS version of Node.js and NPM.

npx prisma migrate deploy # Create the database schema
npm run cli # Start the CLI

Demo of the CLI

Alternatively, you can bootstrap the database manually. Read more on the Bootstrapping article.

When you are done, it's highly recommended to modify your docker-compose.yml so that only the API service has its port(s) exposed to the host machine. For more information or alternative options for deployment, check out the Deployment article.

Developing

Built With

This project is built on NestJS and written in TypeScript. Additional major dependencies include:

The project also depends on the following services:

  • PostgreSQL
  • Redis
  • RabbitMQ

Prerequisites

To run the development environment for this project, you should have Node.js LTS installed, along with NPM. We recommend using NVM to manage your Node.js installation. If you are on Windows, you can use NVM for Windows.

While the API does not run in Docker for development, you should have Docker and Docker Compose installed for running the other services which the API depends on. This project has been tested on Docker Engine version 20.10. Docker Compose must be version 1.28.0 or higher.

Git must also be installed to clone the repository and manage your changes.

Setting up Dev

Start by cloning the repository. We recommend using SSH for this, as it is more secure, but you can use HTTPS if you prefer or are not able to use SSH.

git clone [email protected]:rpitv/glimpse-api.git
cd glimpse-api/
npm i

After that, you will need to create your .env file. You can use the .env.sample file as a template, and then modify it to match your environment.

cp .env.sample .env

Once you have your .env file, you will need to create a .env.docker file. Your .env.docker file should be the same as your .env file, except any references to localhost need to be replaced with their corresponding Docker service names. For example, if you have RABBITMQ_URL= RABBITMQ_URL=amqp://localhost:5672, you would change it to RABBITMQ_URL= RABBITMQ_URL=amqp://rabbitmq:5672.

Now you can start up the Docker containers which the API depends on.

docker compose up -d

Once the database is up and running, you can use Prisma and the CLI to bootstrap your database.

npx prisma migrate deploy # Create the database schema
npm run cli # Start the CLI

Demo of the CLI

Once your database is initialized, you can start the API.

npm run start:dev

The API will be available at http://localhost:4000. A GraphQL playground will be available at http://localhost:4000/graphql.

Building

In order to build the project, you will need to complete all of the above steps in the Setting up Dev section. The project can then be built using the build command:

npm run build

The built files will be in the dist/ directory.

Deploying / Publishing

The built project can be run using the start:prod command:

npm run start:prod

Alternatively, run the JavaScript file directly:

node dist/src/main

Versioning

This project is still in early beta and proper versioning has not yet been set up. However, we will likely be implementing SemVer versioning at some point in the future.

Configuration

Currently, all configuration for this project is controlled via .env files. You can use the .env.sample file as a template, and then modify it to match your environment. .env.docker is used for Docker deployments, and should be the same as your .env file, except any references to localhost need to be replaced with their corresponding Docker service names.

Environment Variables

Name Default/Required Description
NODE_ENV development The environment in which this application is running. May be: production, development, staging, test
PORT 4000 The port to run the NestJS server on.
DATABASE_URL Required A PostgreSQL connection URI for your database.
REDIS_URL Required A Redis connection URI for your Redis database.
RABBITMQ_URL Required A RabbitMQ connection URI for your RabbitMQ server.
SESSION_SECRET Required A random string used to hash session IDs. Must be at least 64 characters long. Read more
SESSION_NAME glimpse.sid The name to use within session cookies and in the Redis session store.
LOGIN_REDIRECT_COOKIE_NAME glimpse.redirect The name of the cookie to store the login redirect URL in. This cookie is ephemeral, with a max life of 10 minutes.
OAUTH_SUCCESS_REDIRECT / The relative URI to redirect users to after a successful OAuth login. Should not include query parameters.
OAUTH_FAIL_REDIRECT /login The relative URI to redirect users to after a failed OAuth login. Should not include query parameters.
LOGIN_REDIRECT_HOSTS Optional A comma-separated list of hostnames that should be trusted in post-login user-provided redirects. Default is empty, which only allows relative redirects. E.g. rpi.tv,rpitv.org
DISCORD_CLIENT_ID Required The client ID for your Discord application, allowing for OAuth sign-in. Currently required.
DISCORD_CLIENT_SECRET Required The secret corresponding to the above Discord client ID.
DISCORD_CALLBACK_URL Required The URL which users should be redirected to by Discord after they've approved the OAuth authentication request. Must be an absolute URL. Should be the /auth/discord endpoint, but through a reverse proxy.
TRUST_PROXY Required Option defining when to trust proxied requests. This application is designed to run behind a reverse proxy, but this can be set to false to disable trusting proxies completely. Set as restrictive as possible. Read more
HTTPS true in production and staging True/false value for whether the reverse proxy this application is running behind is HTTPS-only. This is used to mark cookies as Secure-only. You probably want this set to true.
LOG_LEVELS log,warn,error in production A comma-separated list of NestJS log levels that should be enabled. Valid values are: verbose, debug, log, warn, error.

Tests

The test suite is currently incomplete. You can run the tests using the test command:

npm run test

Style guide

This project generally follows the guidelines found here: https://github.com/elsewhencode/project-guidelines

Code style is enforced using ESLint and Prettier. When you set up your development environment, as long as you follow the provided instructions, a Git hook will be installed which will automatically format your code pre-commit. If you'd like to do this manually, you can pass the --ignore-scripts flag to npm i to skip the Git hook installation and run npm run format before committing your code.

An .editorconfig file is provided to help your editor automatically format your code to match the project's style.

API Reference

GraphQL

The API is primarily controlled via GraphQL, which has built-in documentation. Please introspect the API to view available documentation.

HTTP

GET /auth/discord

Query Parameters

All query parameters are optional.

  • redirect - The URL to redirect to after authentication. Can be relative or absolute.
  • code - The Discord OAuth2 code. This is automatically provided by Discord after a successful authentication.
  • state - The state parameter for the OAuth2 request. This is automatically provided by Discord after a successful authentication.
  • error - The error parameter for the OAuth2 request. This is automatically provided by Discord after a failed authentication.
  • error_description - The error description parameter for the OAuth2 request. This is automatically provided by Discord after a failed authentication.
Response

The response will vary depending on whether the code or error query parameters are present.

  • If neither code nor error are present, the user will be redirected to Discord to authenticate. If the redirect query parameter is present, it will be saved for when the user returns from Discord. None of the other query parameters have any effect in this state.
  • If either code or error are present, there are three possible error states:
    • invalid_code - The code parameter was present, but Discord did not return a valid access token (e.g. the code was invalid or expired).
    • server_error - The error parameter was present, indicating that Discord encountered an error while attempting to authenticate the user.
    • no_user - The code parameter was present, but Discord returned a valid access token for an account that does not correspond to a user within the database.
  • If any of the three above errors occur, the user will be redirected to <fail_redirect>?error=<error>. If the redirect query parameter was present when state was generated, the user will be redirected to <fail_redirect>?error=<error>&redirect=<redirect>.
  • If none of the errors occurred, the user will be redirected to <success_redirect>, unless the redirect query parameter was present when the state was generated, in which case the user will be redirected to the URL that was specified. A new sessioncookie will also be provided in the response.

Note that this API is intended to be proxied in production, e.g. to /api. The URLs that the user can be redirected to should be implemented elsewhere (e.g. rpitv/glimpse-ui), and will return a 404 if the API is not being accessed through a proxy.

Database

This project uses PostgreSQL v14 within it's default Docker containers. If you wish to run your database outside of Docker, no other version has been tested.

Redis v7 is also used for state and session management.

Licensing

This project is licensed under the MIT License - see the LICENSE file for details.

glimpse-api's People

Contributors

robere2 avatar superultramegachicken avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

glimpse-api's Issues

createPerson without providing a start date fails

When you attempt to create a new Person using GraphQL, it responds with the following error:

{
  "errors": [
    {
      "message": "Unexpected error.",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "createPerson"
      ],
      "extensions": {
        "originalError": {
          "message": "\nInvalid `)).create()` invocation in\n/usr/src/app/src/CrudGenerator.ts:286:12\n\n  283 // Create the object. Permissions have already been checked by @Auth directives.\n  284 return await (<any>(\n  285     ctx.prisma[modelNameToPrismaDelegateMap[modelName]]\n→ 286 )).create({\n        data: {\n          name: 'Billy Wu',\n      +   start: DateTime,\n      ?   pronouns?: String | null,\n      ?   graduation?: DateTime | null,\n      ?   end?: DateTime | null,\n      ?   description?: String | null,\n      ?   blogPosts?: {\n      ?     create?: BlogPostCreateWithoutAuthorInput | BlogPostCreateWithoutAuthorInput | BlogPostUncheckedCreateWithoutAuthorInput | BlogPostUncheckedCreateWithoutAuthorInput,\n      ?     connectOrCreate?: BlogPostCreateOrConnectWithoutAuthorInput | BlogPostCreateOrConnectWithoutAuthorInput,\n      ?     createMany?: BlogPostCreateManyAuthorInputEnvelope,\n      ?     connect?: BlogPostWhereUniqueInput | BlogPostWhereUniqueInput\n      ?   },\n      ?   credits?: {\n      ?     create?: CreditCreateWithoutPersonInput | CreditCreateWithoutPersonInput | CreditUncheckedCreateWithoutPersonInput | CreditUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: CreditCreateOrConnectWithoutPersonInput | CreditCreateOrConnectWithoutPersonInput,\n      ?     createMany?: CreditCreateManyPersonInputEnvelope,\n      ?     connect?: CreditWhereUniqueInput | CreditWhereUniqueInput\n      ?   },\n      ?   images?: {\n      ?     create?: PersonImageCreateWithoutPersonInput | PersonImageCreateWithoutPersonInput | PersonImageUncheckedCreateWithoutPersonInput | PersonImageUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: PersonImageCreateOrConnectWithoutPersonInput | PersonImageCreateOrConnectWithoutPersonInput,\n      ?     createMany?: PersonImageCreateManyPersonInputEnvelope,\n      ?     connect?: PersonImageWhereUniqueInput | PersonImageWhereUniqueInput\n      ?   },\n      ?   roles?: {\n      ?     create?: RoleCreateWithoutPersonInput | RoleCreateWithoutPersonInput | RoleUncheckedCreateWithoutPersonInput | RoleUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: RoleCreateOrConnectWithoutPersonInput | RoleCreateOrConnectWithoutPersonInput,\n      ?     createMany?: RoleCreateManyPersonInputEnvelope,\n      ?     connect?: RoleWhereUniqueInput | RoleWhereUniqueInput\n      ?   },\n      ?   users?: {\n      ?     create?: UserCreateWithoutPersonInput | UserCreateWithoutPersonInput | UserUncheckedCreateWithoutPersonInput | UserUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: UserCreateOrConnectWithoutPersonInput | UserCreateOrConnectWithoutPersonInput,\n      ?     createMany?: UserCreateManyPersonInputEnvelope,\n      ?     connect?: UserWhereUniqueInput | UserWhereUniqueInput\n      ?   }\n        }\n      })\n\nArgument start for data.start is missing.\n\nNote: Lines with + are required, lines with ? are optional.\n",
          "stack": "Error: \nInvalid `)).create()` invocation in\n/usr/src/app/src/CrudGenerator.ts:286:12\n\n  283 // Create the object. Permissions have already been checked by @Auth directives.\n  284 return await (<any>(\n  285     ctx.prisma[modelNameToPrismaDelegateMap[modelName]]\n→ 286 )).create({\n        data: {\n          name: 'Billy Wu',\n      +   start: DateTime,\n      ?   pronouns?: String | null,\n      ?   graduation?: DateTime | null,\n      ?   end?: DateTime | null,\n      ?   description?: String | null,\n      ?   blogPosts?: {\n      ?     create?: BlogPostCreateWithoutAuthorInput | BlogPostCreateWithoutAuthorInput | BlogPostUncheckedCreateWithoutAuthorInput | BlogPostUncheckedCreateWithoutAuthorInput,\n      ?     connectOrCreate?: BlogPostCreateOrConnectWithoutAuthorInput | BlogPostCreateOrConnectWithoutAuthorInput,\n      ?     createMany?: BlogPostCreateManyAuthorInputEnvelope,\n      ?     connect?: BlogPostWhereUniqueInput | BlogPostWhereUniqueInput\n      ?   },\n      ?   credits?: {\n      ?     create?: CreditCreateWithoutPersonInput | CreditCreateWithoutPersonInput | CreditUncheckedCreateWithoutPersonInput | CreditUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: CreditCreateOrConnectWithoutPersonInput | CreditCreateOrConnectWithoutPersonInput,\n      ?     createMany?: CreditCreateManyPersonInputEnvelope,\n      ?     connect?: CreditWhereUniqueInput | CreditWhereUniqueInput\n      ?   },\n      ?   images?: {\n      ?     create?: PersonImageCreateWithoutPersonInput | PersonImageCreateWithoutPersonInput | PersonImageUncheckedCreateWithoutPersonInput | PersonImageUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: PersonImageCreateOrConnectWithoutPersonInput | PersonImageCreateOrConnectWithoutPersonInput,\n      ?     createMany?: PersonImageCreateManyPersonInputEnvelope,\n      ?     connect?: PersonImageWhereUniqueInput | PersonImageWhereUniqueInput\n      ?   },\n      ?   roles?: {\n      ?     create?: RoleCreateWithoutPersonInput | RoleCreateWithoutPersonInput | RoleUncheckedCreateWithoutPersonInput | RoleUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: RoleCreateOrConnectWithoutPersonInput | RoleCreateOrConnectWithoutPersonInput,\n      ?     createMany?: RoleCreateManyPersonInputEnvelope,\n      ?     connect?: RoleWhereUniqueInput | RoleWhereUniqueInput\n      ?   },\n      ?   users?: {\n      ?     create?: UserCreateWithoutPersonInput | UserCreateWithoutPersonInput | UserUncheckedCreateWithoutPersonInput | UserUncheckedCreateWithoutPersonInput,\n      ?     connectOrCreate?: UserCreateOrConnectWithoutPersonInput | UserCreateOrConnectWithoutPersonInput,\n      ?     createMany?: UserCreateManyPersonInputEnvelope,\n      ?     connect?: UserWhereUniqueInput | UserWhereUniqueInput\n      ?   }\n        }\n      })\n\nArgument start for data.start is missing.\n\nNote: Lines with + are required, lines with ? are optional.\n\n    at Document.validate (/usr/src/app/node_modules/@prisma/client/runtime/index.js:47823:20)\n    at PrismaClient._executeRequest (/usr/src/app/node_modules/@prisma/client/runtime/index.js:49972:17)\n    at consumer (/usr/src/app/node_modules/@prisma/client/runtime/index.js:49916:23)\n    at /usr/src/app/node_modules/@prisma/client/runtime/index.js:49920:76\n    at runInChildSpan (/usr/src/app/node_modules/@prisma/client/runtime/index.js:49133:12)\n    at /usr/src/app/node_modules/@prisma/client/runtime/index.js:49920:20\n    at AsyncResource.runInAsyncScope (node:async_hooks:203:9)\n    at PrismaClient._request (/usr/src/app/node_modules/@prisma/client/runtime/index.js:49919:86)\n    at /usr/src/app/node_modules/@prisma/client/runtime/index.js:46474:25\n    at _callback (/usr/src/app/node_modules/@prisma/client/runtime/index.js:46233:52)"
        }
      }
    }
  ],
  "data": null
}

Adding a start variable to the input resolves this issue, however the start variable is intended to be optional, defaulting to the current time.

Meta: Documentation

Documentation is lackluster for this repository. I have been working on improving it within the repository's wiki. A set of goals needs to be outlined for what we want documented in order for this issue to be considered "complete"

  • Introduction/NestJS
  • Authentication flow (Passport.js)
  • Authorization flow (@rule)
  • Query complexities
  • CASL integration
  • Prisma integration/migrations
  • Prisma types
  • StreamModule
  • Glossary
  • GraphQL Input types
  • Database bootstrapping
  • Deployment/Docker

https://github.com/rpitv/glimpse-api/wiki

This is carried over from #53

Improve Video datatype in schema

The current Video datatype isn't strict at all: it allows for its videoType property to be any integer, and it's data property to contain any data, some which may or may not be related to the videoType, and may not contain required data. This should be improved, but I'm not sure what the best way to go about it is yet.

Feature: Discord integration

Ideally, almost everything the user can do on the website, they'd be able to do in Discord, and vice versa. Consequently, any updates to the website should also update Discord and vice versa.

This will likely require heavily marrying this repository with https://github.com/rpitv/glimpse-discord: perhaps to the point where it doesn't even make sense to have them as two separate repositories anymore.

I think a good starting point may be to set up the API to communciate with glimpse-discord via RabbitMQ to notify it when to create a new Discord channel. From there, we can evaluate whether it makes sense to continue over RabbitMQ or to merge codebases entirely.

Meta: Build Workflow running slow

I've noticed that the build action takes quite a long time: ~40 minutes, as opposed to ~1-2 minutes on my local computer. We should try to improve this through caching, if possible. If we aren't able to get build times below 10 minutes, some sort of backup plan could be integrated for critical hotfixes.

Feature: Running in HTTPS

While this application is designed to run behind a proxy, there is still a slight security benefit to still keeping things encrypted behind the proxy. It would be nice if we could add support to run this application with HTTPS enabled. This will require coordination with https://github.com/rpitv/glimpse-ui.

Note that, while more secure, running the app with HTTPS enabled will require slightly more processing time. It isn't necessarily something that we should just do because we can, but it would be good to have the option.

CRUD generator doesn't include a "count" property

When using pagination, a total number of documents is required to set up pagination correctly. It's not practical (let alone possible) to request all documents at once to get the total count. A simple "count" generator should be created in the CRUD generator which returns this value. For example, if 12 groups exist in the database, then querying "groupCount" would return 12.

In terms of what permissions this would require, it should return all the documents which match the filter from getAccessibleByFilter. This is what is checked for each individual record as well. Any documents which the user does not have permission to read any field on should not be included in the count.

Add Prisma generator to generate CRUD resolvers

The current CrudGenerator is janky and hard to maintain. It also has unsafe type casting due to the limitations of Prisma. Adding a Prisma generator should hopefully resolve these issues.

Authentication Support

The database currently does not support any methods of authentication or authorization. This must be changed before deployment.

NestJS Framework

The current foundation of the API is confusing and poorly documented. Due to the security and stability implications involved, it's important to make sure developers have a good understanding of the code that they are working on. Transitioning to a highly supported and well documented framework seems like the best first step to doing this, and NestJS is the current obvious choice.

Work on this issue is already being done in the nestjs branch.

Feature todo list (bolded are required for merge):

  • GraphQL API
    • findManyX parameters
      • Pagination
        • Cursor-based pagination
        • Offset-based pagination
      • Sorting
        • Sorting authorization
      • Filtering
        • Filtering authorization
    • Subscriptions
    • Query complexity restrictions
  • Authenticaction
    • Local via username and password
    • Discord via OAuth
    • RPI via SAML
    • Session storage
  • Authorization
    • findOneX resolvers
    • findManyX resolvers
    • createX resolvers
    • updateX resolvers
    • deleteX resolvers
    • count resolvers
  • Prisma ORM
    • Generic Prisma resolver generator (DRY)
  • Image uploading
  • Test suite
    • Resolver tests
    • Authorization tests
    • Authentication tests
  • In-depth documentation

Anything not completed by merge should likely have it's own issue created for it.

Bug: Input variables can only be used in the top level of queries

The current StartStream GraphQL snippet in https://github.com/rpitv/glimpse-ui is as follows:

mutation StartStream($to: String!, $from: String!) {
  createStream(input: {to: $to, from: $from}) {
    from
    to
  }
}

Currently, the API will complain about this, as variables are only allowed to be used at the top level of query inputs, e.g.:

mutation StartStream($input: CreateStreamInput!) {
  createStream(input: $input) {
    from
    to
  }
}

The fix in #61 may have broken this further, and as a result, a release should not be made until this issue is resolved and/or further testing is done.

Add Docker support

Vagrant is currently supported, however a Docker file should also be written for production.

Login via RPI/Discord

Login via username/password is the only way to login at the moment. Login via Discord and RPI account would speed things up for the user and take the burden of password management off of Glimpse.

To be clear, Glimpse must still support passwords; not everyone will have Discord or RPI account, and passwords are required for LDAP. However, for users who do not use LDAP and have a Discord or RPI account, passwords wouldn't need to be stored for these accounts if they don't set them up.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.