├── .dockerignore ├── .env.example ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── cargo.yml │ ├── docker.yml │ └── publish.yml ├── .gitignore ├── .gitmodules ├── .graphqlrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Caddyfile ├── Caddyfile.dev ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── config_example.toml ├── docker-compose.dev.yml ├── docker-compose.yml ├── graphql ├── github │ ├── schema.json │ └── users │ │ └── authenticated_user.graphql └── rcos │ ├── discord_associations │ ├── project │ │ ├── create_project_channel.graphql │ │ ├── create_project_role.graphql │ │ ├── find_project.graphql │ │ └── projects.graphql │ └── small_group │ │ ├── create_small_group_category.graphql │ │ ├── create_small_group_channel.graphql │ │ ├── create_small_group_role.graphql │ │ ├── find_small_group.graphql │ │ └── small_groups.graphql │ ├── meetings │ ├── authorization_for.graphql │ ├── creation │ │ ├── context.graphql │ │ ├── create.graphql │ │ └── host_selection.graphql │ ├── delete.graphql │ ├── edit │ │ ├── edit.graphql │ │ └── host_selection.graphql │ ├── get.graphql │ ├── get_by_id.graphql │ └── get_host.graphql │ ├── projects │ ├── authorization_for.graphql │ ├── create.graphql │ ├── get_by_id.graphql │ └── projects.graphql │ ├── schema.json │ ├── semesters │ ├── current │ │ ├── coordinators.graphql │ │ ├── info.graphql │ │ └── mentors.graphql │ ├── get.graphql │ ├── get_by_id.graphql │ └── mutations │ │ ├── create.graphql │ │ └── edit.graphql │ ├── stats │ └── landing_page.graphql │ └── users │ ├── accounts │ ├── for_user.graphql │ ├── link.graphql │ ├── lookup.graphql │ ├── reverse_lookup.graphql │ └── unlink.graphql │ ├── create_one.graphql │ ├── delete.graphql │ ├── developers.graphql │ ├── discord_whois.graphql │ ├── edit_profile.graphql │ ├── enrollments │ ├── edit_enrollment.graphql │ ├── enrollment_by_ids.graphql │ ├── enrollments_lookup.graphql │ └── user_enrollment_lookup.graphql │ ├── navbar_authentication.graphql │ ├── profile.graphql │ └── role_lookup.graphql ├── proposals ├── fall-2020.md ├── fall-2021.md ├── fall-2022.md ├── spring-2021.md ├── spring-2022.md ├── summer-2020.md └── summer-2021.md ├── rcos-data ├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── config.yaml ├── config │ ├── Caddyfile │ └── telescope-config.toml ├── docker-compose.yml ├── example_queries │ ├── current_semester_projects.gql │ ├── enrolled_and_no_project.gql │ ├── semester_coordinators.gql │ ├── semester_counts.gql │ ├── semester_enrollments.gql │ ├── semester_projects.gql │ ├── semester_small_groups.gql │ └── users_with_enrollments.gql ├── metadata │ ├── actions.graphql │ ├── actions.yaml │ ├── allow_list.yaml │ ├── cron_triggers.yaml │ ├── functions.yaml │ ├── network.yaml │ ├── query_collections.yaml │ ├── remote_schemas.yaml │ ├── tables.yaml │ └── version.yaml └── migrations │ ├── 1610903455000_setup │ ├── down.sql │ └── up.sql │ ├── 1610903832000_create_types │ ├── down.sql │ └── up.sql │ ├── 1610904400000_create_semesters_table │ ├── down.sql │ └── up.sql │ ├── 1610906701000_create_announcements_table │ ├── down.sql │ └── up.sql │ ├── 1610906743000_create_users_table │ ├── down.sql │ └── up.sql │ ├── 1610906899000_create_user_accounts_table │ ├── down.sql │ └── up.sql │ ├── 1610907783000_create_projects_table │ ├── down.sql │ └── up.sql │ ├── 1610907975000_create_project_presentations_table │ ├── down.sql │ └── up.sql │ ├── 1610908265000_create_enrollments_table │ ├── down.sql │ └── up.sql │ ├── 1610908266000_create_mentor_proposals_table │ ├── down.sql │ └── up.sql │ ├── 1610908267000_create_workshop_proposals_table │ ├── down.sql │ └── up.sql │ ├── 1610908268000_create_pay_requests_table │ ├── down.sql │ └── up.sql │ ├── 1610908269000_create_project_pitches_table │ ├── down.sql │ └── up.sql │ ├── 1610908437000_create_small_groups_table │ ├── down.sql │ └── up.sql │ ├── 1610908550000_create_small_group_projects_table │ ├── down.sql │ └── up.sql │ ├── 1610908717000_create_small_group_mentors_table │ ├── down.sql │ └── up.sql │ ├── 1610908840000_create_status_update_table │ ├── down.sql │ └── up.sql │ ├── 1610908902000_create_status_update_submissions_table │ ├── down.sql │ └── up.sql │ ├── 1610909370000_create_meetings_table │ ├── down.sql │ └── up.sql │ ├── 1610909519000_create_meeting_attendances_table │ ├── down.sql │ └── up.sql │ ├── 1610909635000_create_bonus_attendances_table │ ├── down.sql │ └── up.sql │ ├── 1610909920000_create_project_presentation_grades_table │ ├── down.sql │ └── up.sql │ ├── 1610910056000_create_chat_associations_table │ ├── down.sql │ └── up.sql │ ├── 1610910121000_create_final_grade_appeal_table │ ├── down.sql │ └── up.sql │ ├── 1610910395000_create_small_group_members_view │ ├── down.sql │ └── up.sql │ ├── 1610910455000_create_public_meetings_view │ ├── down.sql │ └── up.sql │ ├── 1610910643000_create_faculty_advisors_view │ ├── down.sql │ └── up.sql │ ├── 1610910650000_create_coordinators_view │ ├── down.sql │ └── up.sql │ ├── 1610912853000_create_roles │ ├── down.sql │ └── up.sql │ ├── 1611347809000_grant_api_user_to_authenticator │ ├── down.sql │ └── up.sql │ ├── 1611354573000_simplify_url_domain │ ├── down.sql │ └── up.sql │ ├── 1612823016000_add_is_external_project_field │ ├── down.sql │ └── up.sql │ ├── 1612823449000_remove_project_languages_field │ ├── down.sql │ └── up.sql │ ├── 1612831682000_add_external_organizations_table │ ├── down.sql │ └── up.sql │ ├── 1612831911000_add_external_org_key_to_projects │ ├── down.sql │ └── up.sql │ ├── 1613612582809_create_duplicate_users_view │ ├── down.sql │ └── up.sql │ ├── 1614142201290_remove_unauthenticated_roles │ ├── down.sql │ └── up.sql │ ├── 1615336759471_add_user_ids │ ├── down.sql │ └── up.sql │ ├── 1615338200877_alter_user_accounts_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615344082163_alter_bonus_attendances_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615401240741_alter_enrollments_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615401726640_alter_final_grade_appeals_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615403071311_alter_meeting_attendances_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615403588212_alter_meetings_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615404324499_alter_mentor_proposals_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615406430290_alter_pay_requests_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615407313244_alter_project_pitches_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615408637140_alter_project_presentation_grades_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615409192551_alter_small_group_mentors_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615409806039_alter_status_update_submissions_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615410882634_alter_workshop_proposals_users_foreign_key │ ├── down.sql │ └── up.sql │ ├── 1615925578577_prevent_null_enrollment_fields │ ├── down.sql │ └── up.sql │ ├── 1615926534831_constrain_user_accounts_unique │ ├── down.sql │ └── up.sql │ ├── 1616616058766_prevent_null_meeting_fields │ ├── down.sql │ └── up.sql │ ├── 1618351261768_alter_meetings_is_public │ ├── down.sql │ └── up.sql │ ├── 1622138042193_add_sysadmin_role │ ├── down.sql │ └── up.sql │ ├── 1622873051771_separate_chat_associations │ ├── down.sql │ └── up.sql │ ├── 1623899398293_alter_meeetings_agenda │ ├── down.sql │ └── up.sql │ ├── 1636145654065_remove_usernames │ ├── down.sql │ └── up.sql │ ├── 1649450066130_alter_table_public_projects_add_column_updated_at │ ├── down.sql │ └── up.sql │ ├── 1649450545541_create_projects_update_trigger │ ├── down.sql │ └── up.sql │ ├── 1666990035352_create_table_public_project_stack │ ├── down.sql │ └── up.sql │ ├── 1666990131409_alter_table_public_project_stack_alter_column_repo │ ├── down.sql │ └── up.sql │ ├── 1666990322555_alter_table_public_project_stack_add_column_project_id │ ├── down.sql │ └── up.sql │ ├── 1666990415144_alter_table_public_project_stack_drop_column_project_id │ ├── down.sql │ └── up.sql │ ├── 1666990471268_alter_table_public_project_stack_add_column_project_id │ ├── down.sql │ └── up.sql │ ├── 1666990490497_set_fk_public_project_stack_project_id │ ├── down.sql │ └── up.sql │ └── 1667334258367_create_table_public_project_repositories │ ├── down.sql │ └── up.sql ├── rustfmt.toml ├── src ├── api │ ├── discord │ │ └── mod.rs │ ├── github │ │ ├── mod.rs │ │ └── users │ │ │ ├── authenticated_user.rs │ │ │ └── mod.rs │ ├── mod.rs │ └── rcos │ │ ├── auth.rs │ │ ├── discord_associations │ │ ├── mod.rs │ │ ├── project │ │ │ ├── create_project_channel.rs │ │ │ ├── create_project_role.rs │ │ │ ├── mod.rs │ │ │ └── project_info.rs │ │ └── small_group │ │ │ ├── create_small_group_category.rs │ │ │ ├── create_small_group_channel.rs │ │ │ ├── create_small_group_role.rs │ │ │ ├── mod.rs │ │ │ └── small_group_info.rs │ │ ├── landing_page_stats.rs │ │ ├── meetings │ │ ├── authorization_for.rs │ │ ├── creation │ │ │ ├── context.rs │ │ │ ├── create.rs │ │ │ ├── host_selection.rs │ │ │ └── mod.rs │ │ ├── delete.rs │ │ ├── edit.rs │ │ ├── get.rs │ │ ├── get_by_id.rs │ │ ├── get_host.rs │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── prelude.rs │ │ ├── projects │ │ ├── authorization_for.rs │ │ ├── create.rs │ │ ├── get_by_id.rs │ │ ├── mod.rs │ │ └── projects_page.rs │ │ ├── search_strings.rs │ │ ├── semesters │ │ ├── current │ │ │ ├── info.rs │ │ │ └── mod.rs │ │ ├── get.rs │ │ ├── get_by_id.rs │ │ ├── mod.rs │ │ └── mutations │ │ │ ├── create.rs │ │ │ ├── edit.rs │ │ │ └── mod.rs │ │ └── users │ │ ├── accounts │ │ ├── for_user.rs │ │ ├── link.rs │ │ ├── lookup.rs │ │ ├── mod.rs │ │ ├── reverse_lookup.rs │ │ └── unlink.rs │ │ ├── create.rs │ │ ├── delete.rs │ │ ├── developers_page.rs │ │ ├── discord_whois.rs │ │ ├── edit_profile.rs │ │ ├── enrollments │ │ ├── edit_enrollment.rs │ │ ├── enrollment_by_ids.rs │ │ ├── enrollments_lookup.rs │ │ ├── mod.rs │ │ └── user_enrollment_lookup.rs │ │ ├── mod.rs │ │ ├── navbar_auth.rs │ │ ├── profile.rs │ │ └── role_lookup.rs ├── app_data.rs ├── discord_bot │ ├── commands │ │ ├── associate.rs │ │ ├── generate.rs │ │ ├── mod.rs │ │ └── whois.rs │ ├── event_handler.rs │ └── mod.rs ├── env.rs ├── error.rs ├── main.rs ├── templates │ ├── auth.rs │ ├── helpers.rs │ ├── jumbotron.rs │ ├── mod.rs │ ├── navbar.rs │ ├── page.rs │ ├── pagination.rs │ ├── static_pages │ │ ├── mod.rs │ │ └── sponsors.rs │ └── tags.rs └── web │ ├── csrf.rs │ ├── middlewares │ ├── authorization.rs │ ├── error_rendering.rs │ └── mod.rs │ ├── mod.rs │ └── services │ ├── admin │ ├── mod.rs │ └── semesters │ │ ├── create.rs │ │ ├── edit.rs │ │ ├── mod.rs │ │ └── view_enrollments.rs │ ├── auth │ ├── identity.rs │ ├── mod.rs │ ├── oauth2_providers │ │ ├── discord.rs │ │ ├── github.rs │ │ └── mod.rs │ └── rpi_cas.rs │ ├── coordinate │ ├── enrollments │ │ ├── edit.rs │ │ └── mod.rs │ └── mod.rs │ ├── index.rs │ ├── meetings │ ├── create.rs │ ├── delete.rs │ ├── edit.rs │ ├── list.rs │ ├── mod.rs │ └── view.rs │ ├── mod.rs │ ├── not_found.rs │ ├── projects │ ├── create.rs │ ├── list.rs │ ├── mod.rs │ └── view.rs │ └── user │ ├── delete.rs │ ├── developers.rs │ ├── join_discord.rs │ ├── login.rs │ ├── mod.rs │ ├── profile.rs │ └── register.rs ├── static ├── icons │ └── telescope │ │ ├── v1.svg │ │ ├── v2-black.png │ │ ├── v2-transparent.png │ │ ├── v2-white.png │ │ ├── v2.svg │ │ ├── v3-black.png │ │ ├── v3-transparent.png │ │ ├── v3-white.png │ │ └── v3.svg ├── scripts │ └── script.js ├── sponsors │ ├── google.svg │ ├── hfoss.webp │ ├── microsoft.svg │ ├── mozilla.svg │ ├── osi.webp │ └── red-hat.svg └── styles │ └── base.css └── templates ├── admin ├── index.hbs └── semesters │ ├── enrollments.hbs │ ├── forms │ ├── create.hbs │ ├── edit.hbs │ ├── feedback.hbs │ └── interactivity.hbs │ └── index.hbs ├── auth.hbs ├── coordinate ├── enrollments │ ├── edit │ │ └── form.hbs │ └── index.hbs └── index.hbs ├── index.hbs ├── jumbotron.hbs ├── meetings ├── card.hbs ├── creation │ ├── finish.hbs │ └── host_selection.hbs ├── edit │ ├── form.hbs │ └── host_selection.hbs ├── link.hbs ├── list.hbs ├── page.hbs └── title.hbs ├── navbar.hbs ├── ogp_tags.hbs ├── page.hbs ├── pagination ├── link.hbs ├── pagination_bar.hbs └── separator.hbs ├── projects ├── card.hbs ├── card_copy.hbs ├── list.hbs └── page.hbs ├── static └── sponsors.hbs └── user ├── delete.hbs ├── developers.hbs ├── profile.hbs ├── register.hbs └── settings.hbs /.dockerignore: -------------------------------------------------------------------------------- 1 | ./target 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Note that these secrets are completely arbitrary and can be whatever you want for development purposes. 2 | 3 | # Used by Hasura 4 | DATABASE_URL=postgres://postgres:EXAMPLE_POSTGRES_PASSWORD@db/postgres?sslmode=disable 5 | 6 | # The JWT secret used with Hasura. 7 | RCOS_JWT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx 8 | # The admin secret used with Hasura, MUST BE AT LEAST 32 characters long! 9 | HASURA_GRAPHQL_ADMIN_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 10 | 11 | # Used by postgres docker. Replace in your version. Should match the password above. 12 | POSTGRES_PASSWORD=EXAMPLE_POSTGRES_PASSWORD 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Desktop (please complete the following information):** 26 | - OS: [e.g. iOS] 27 | - Browser [e.g. chrome, safari] 28 | - Version [e.g. 22] 29 | 30 | **Smartphone (please complete the following information):** 31 | - Device: [e.g. iPhone6] 32 | - OS: [e.g. iOS8.1] 33 | - Browser [e.g. stock browser, safari] 34 | - Version [e.g. 22] 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: Alfriadox 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "gitsubmodule" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Cargo 4 | 5 | jobs: 6 | build: 7 | name: Cargo Build 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | - uses: actions-rs/cargo@v1 15 | with: 16 | command: build 17 | test: 18 | name: Cargo Test 19 | runs-on: ubuntu-20.04 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: test 28 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Docker 4 | 5 | jobs: 6 | build: 7 | name: Docker Compose 8 | runs-on: ubuntu-20.04 9 | steps: 10 | - uses: actions/checkout@v2 11 | - name: Docker Compose Build 12 | # Create some empty config files and directories that docker 13 | # needs to successfully build telescope 14 | run: touch config.toml && docker-compose build 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | push_to_registry: 7 | name: Push Docker image to GitHub Packages 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out the repo and submodules 11 | uses: actions/checkout@v2 12 | with: 13 | submodules: recursive 14 | - name: Push to GitHub Packages 15 | uses: docker/build-push-action@v1 16 | with: 17 | username: ${{ github.actor }} 18 | password: ${{ secrets.GITHUB_TOKEN }} 19 | registry: docker.pkg.github.com 20 | repository: rcos/telescope/telescope 21 | tag_with_ref: true 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | .DS_Store 4 | # .env is to store development env variables. 5 | .env 6 | 7 | # user config 8 | config.toml 9 | 10 | # The Clion GraphQL Plugin config file in the GraphQL folder may contain 11 | # secrets, and should be ignored. 12 | graphql/rcos/.graphqlconfig 13 | graphql/rcos/schema.graphql 14 | graphql/github/schema.graphql 15 | graphql/github/.graphqlconfig 16 | 17 | # All future changes to the Caddyfile should be ignored due to it needing changes on macOS docker 18 | Caddyfile.dev 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rcos-branding"] 2 | path = static/icons/rcos-branding 3 | url = https://github.com/rcos/rcos-branding.git 4 | -------------------------------------------------------------------------------- /.graphqlrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://cdn.jsdelivr.net/gh/kamilkisiela/graphql-config@v4.3.0/config-schema.json", 3 | "projects": { 4 | "github": { 5 | "schema": ["./graphql/github/schema.json"], 6 | "documents": ["./graphql/github/**/*.graphql"] 7 | }, 8 | "rcos": { 9 | "schema": ["./graphql/rcos/schema.json"], 10 | "documents": ["./graphql/rcos/**/*.graphql"] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Caddyfile: -------------------------------------------------------------------------------- 1 | localhost:8001 { 2 | encode zstd gzip 3 | reverse_proxy hasura:8080 4 | } 5 | 6 | localhost:443 { 7 | encode zstd gzip 8 | reverse_proxy telescope:80 9 | } 10 | -------------------------------------------------------------------------------- /Caddyfile.dev: -------------------------------------------------------------------------------- 1 | localhost:8001 { 2 | encode zstd gzip 3 | reverse_proxy hasura:8080 4 | } 5 | 6 | localhost:443 { 7 | encode zstd gzip 8 | reverse_proxy { 9 | lb_policy first 10 | lb_retries 2 11 | to 172.18.0.1:8080 host.internal:8080 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use latest rust (by explicit version to avoid getting a stale release) 2 | FROM rust:1.58.1 3 | 4 | # Set timezone 5 | ENV TZ=America/New_York 6 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 7 | 8 | # Build Dependencies 9 | WORKDIR /telescope 10 | COPY ./Cargo.* ./ 11 | RUN mkdir src 12 | RUN echo "fn main() {println!(\"BUILD ARTIFACT! \");}" > src/main.rs 13 | RUN cargo build --release 14 | RUN rm -r target/release/deps/telescope* 15 | RUN cargo doc 16 | RUN rm -r target/doc/telescope* 17 | 18 | # Build telescope proper 19 | COPY ./src ./src 20 | COPY ./graphql ./graphql 21 | RUN cargo build --release 22 | # Build documentation 23 | RUN cargo doc 24 | 25 | # Copy over statically served files. 26 | COPY ./static ./static 27 | 28 | # Move the telescope executable to the working directory 29 | RUN mv ./target/release/telescope ./telescope 30 | # Move generated docs to statically served folder 31 | RUN mv ./target/doc/ ./static/internal_docs 32 | # Remove all other build artifacts 33 | RUN rm -r ./target 34 | 35 | # Copy over templates 36 | COPY ./templates ./templates 37 | 38 | # Expose telescope's ports 39 | EXPOSE 80 40 | # Run telescope 41 | ENTRYPOINT ./telescope 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rensselaer Center for Open Source 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | # Development docker-compose file. Will pull up local Postgres database 2 | # and local PostgREST API for running telescope locally. 3 | version: "3.1" 4 | 5 | services: 6 | db: 7 | image: postgres:latest 8 | environment: 9 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" 10 | volumes: 11 | - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock" 12 | - "db_data:/var/lib/postgresql/data" 13 | ports: 14 | - "5432:5432" 15 | 16 | # Hasura Postgres -> GraphQL engine 17 | hasura: 18 | image: hasura/graphql-engine:v2.2.0 19 | restart: unless-stopped 20 | depends_on: 21 | - db 22 | environment: 23 | # https://hasura.io/docs/1.0/graphql/core/deployment/graphql-engine-flags/reference.html 24 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "web_anon" 25 | HASURA_GRAPHQL_DATABASE_URL: "${DATABASE_URL}" 26 | HASURA_GRAPHQL_DEV_MODE: "true" 27 | HASURA_GRAPHQL_ENABLE_CONSOLE: "true" 28 | HASURA_GRAPHQL_JWT_SECRET: "{ \"type\": \"HS256\", \"key\": \"${RCOS_JWT_SECRET}\" }" 29 | HASURA_GRAPHQL_LOG_LEVEL: "info" 30 | HASURA_GRAPHQL_ADMIN_SECRET: "${HASURA_GRAPHQL_ADMIN_SECRET}" 31 | ports: 32 | - "8000:8080" 33 | 34 | # Use caddy for reverse proxy and TLS/SSL certs. 35 | caddy: 36 | image: caddy:2 37 | volumes: 38 | - "caddy_data:/data" 39 | - "${PWD}/Caddyfile.dev:/etc/caddy/Caddyfile" 40 | ports: 41 | - "8001:8001" 42 | - "8443:443" 43 | 44 | 45 | volumes: 46 | db_data: 47 | caddy_data: 48 | 49 | # We need to ensure a stable gateway ip if we want the caddyfile to point to the host each time 50 | networks: 51 | default: 52 | ipam: 53 | config: 54 | - subnet: 172.18.0.0/16 55 | gateway: 172.18.0.1 56 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Development docker-compose file. Will pull up local Postgres database 2 | # and local PostgREST API for running telescope locally. 3 | version: "3.1" 4 | 5 | services: 6 | db: 7 | image: postgres:latest 8 | environment: 9 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" 10 | volumes: 11 | - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock" 12 | - "db_data:/var/lib/postgresql/data" 13 | ports: 14 | - "5432:5432" 15 | 16 | # Hasura Postgres -> GraphQL engine 17 | hasura: 18 | image: hasura/graphql-engine:v2.2.0 19 | restart: unless-stopped 20 | depends_on: 21 | - db 22 | environment: 23 | # https://hasura.io/docs/1.0/graphql/core/deployment/graphql-engine-flags/reference.html 24 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "web_anon" 25 | HASURA_GRAPHQL_DATABASE_URL: "${DATABASE_URL}" 26 | HASURA_GRAPHQL_DEV_MODE: "true" 27 | HASURA_GRAPHQL_ENABLE_CONSOLE: "true" 28 | HASURA_GRAPHQL_JWT_SECRET: "{ \"type\": \"HS256\", \"key\": \"${RCOS_JWT_SECRET}\" }" 29 | HASURA_GRAPHQL_LOG_LEVEL: "info" 30 | HASURA_GRAPHQL_ADMIN_SECRET: "${HASURA_GRAPHQL_ADMIN_SECRET}" 31 | ports: 32 | - "8000:8080" 33 | 34 | # Use caddy for reverse proxy and TLS/SSL certs. 35 | caddy: 36 | image: caddy:2 37 | restart: unless-stopped 38 | volumes: 39 | - "caddy_data:/data" 40 | - "${PWD}/Caddyfile:/etc/caddy/Caddyfile" 41 | ports: 42 | - "8001:8001" 43 | - "8443:443" 44 | 45 | # Telescope itself 46 | telescope: 47 | build: . 48 | image: telescope:latest 49 | depends_on: 50 | - hasura 51 | volumes: 52 | - "${PWD}/config.toml:/telescope/config.toml" 53 | # environment: 54 | # # See the config section about profiles. 55 | # PROFILE: "live" 56 | 57 | volumes: 58 | db_data: 59 | caddy_data: 60 | -------------------------------------------------------------------------------- /graphql/github/users/authenticated_user.graphql: -------------------------------------------------------------------------------- 1 | query AuthenticatedUser { 2 | viewer { 3 | id 4 | login 5 | avatarUrl 6 | url 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/project/create_project_channel.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneProjectChannel( 2 | $project_id: Int!, 3 | $channel_id: String!, 4 | $kind: channel_type!, 5 | ) { 6 | insert_project_channels_one(object: { 7 | project_id: $project_id 8 | channel_id: $channel_id, 9 | kind: $kind, 10 | }) { 11 | channel_id 12 | } 13 | } -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/project/create_project_role.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneProjectRole( 2 | $project_id: Int!, 3 | $role_id: String!, 4 | ) { 5 | insert_project_roles_one(object: { 6 | project_id: $project_id 7 | role_id: $role_id, 8 | }) { 9 | role_id 10 | } 11 | } -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/project/find_project.graphql: -------------------------------------------------------------------------------- 1 | fragment AssociationInfo on projects { 2 | project_id 3 | title 4 | # If project roles are created, what are the role ids. 5 | project_role{ 6 | role_id 7 | } 8 | # If project channel is created, what is the channel id and kind. 9 | project_channels{ 10 | channel_id 11 | kind 12 | } 13 | # Find member who enroll this project. 14 | enrollments{ 15 | user_id 16 | } 17 | } 18 | 19 | 20 | query FindProject( $id: Int!) { 21 | projects( where: { 22 | project_id: {_eq: $id}, 23 | }) { ...AssociationInfo } 24 | } 25 | -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/project/projects.graphql: -------------------------------------------------------------------------------- 1 | fragment AssociationInfo on projects { 2 | project_id 3 | title 4 | # If project roles are created, what are the role ids. 5 | project_role{ 6 | role_id 7 | } 8 | # If project channel is created, what is the channel id and kind. 9 | project_channels{ 10 | channel_id 11 | kind 12 | } 13 | # Find member who enroll this project. 14 | enrollments{ 15 | user_id 16 | } 17 | } 18 | 19 | 20 | query CurrProjects( $offset: Int!, $search: String!, $now: date!) { 21 | projects( offset: $offset, order_by: [{title: asc}], where: { 22 | _or: [ 23 | {title: {_ilike: $search}}, 24 | {description: {_ilike: $search}}, 25 | ], 26 | enrollments: {semester: {start_date: {_lte: $now}, end_date: {_gte: $now}}} 27 | } 28 | ) { ...AssociationInfo } 29 | } 30 | -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/small_group/create_small_group_category.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneSmallGroupCategory( 2 | $small_group_id: Int!, 3 | $category_id: String!, 4 | ) { 5 | insert_small_group_categories_one(object: { 6 | small_group_id: $small_group_id 7 | category_id: $category_id, 8 | }) { 9 | category_id 10 | } 11 | } -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/small_group/create_small_group_channel.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneSmallGroupChannel( 2 | $small_group_id: Int!, 3 | $channel_id: String!, 4 | $kind: channel_type!, 5 | ) { 6 | insert_small_group_channels_one(object: { 7 | small_group_id: $small_group_id 8 | channel_id: $channel_id, 9 | kind: $kind, 10 | }) { 11 | channel_id 12 | } 13 | } -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/small_group/create_small_group_role.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneSmallGroupRole( 2 | $small_group_id: Int!, 3 | $role_id: String!, 4 | ) { 5 | insert_small_group_roles_one(object: { 6 | small_group_id: $small_group_id 7 | role_id: $role_id, 8 | }) { 9 | role_id 10 | } 11 | } -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/small_group/find_small_group.graphql: -------------------------------------------------------------------------------- 1 | fragment SmallGroupAssociationInfo on small_groups { 2 | small_group_id 3 | semester_id 4 | title 5 | 6 | small_group_categories{ 7 | category_id 8 | } 9 | 10 | # If project roles are created, what are the role ids. 11 | small_group_role{ 12 | role_id 13 | } 14 | # If project channel is created, what is the channel id and kind. 15 | small_group_channels{ 16 | channel_id 17 | kind 18 | } 19 | small_group_projects{ 20 | project{ 21 | project_id 22 | title 23 | project_channels{ 24 | channel_id 25 | } 26 | project_role{ 27 | role_id 28 | } 29 | } 30 | } 31 | } 32 | 33 | query FindSmallGroup( $id: Int!) { 34 | small_groups( where: { 35 | small_group_id: {_eq: $id}, 36 | }) { ...SmallGroupAssociationInfo } 37 | } 38 | -------------------------------------------------------------------------------- /graphql/rcos/discord_associations/small_group/small_groups.graphql: -------------------------------------------------------------------------------- 1 | fragment SmallGroupAssociationInfo on small_groups { 2 | small_group_id 3 | semester_id 4 | title 5 | 6 | small_group_categories{ 7 | category_id 8 | } 9 | 10 | # If project roles are created, what are the role ids. 11 | small_group_role{ 12 | role_id 13 | } 14 | # If project channel is created, what is the channel id and kind. 15 | small_group_channels{ 16 | channel_id 17 | kind 18 | } 19 | small_group_projects{ 20 | project{ 21 | project_id 22 | title 23 | project_channels{ 24 | channel_id 25 | } 26 | project_role{ 27 | role_id 28 | } 29 | } 30 | } 31 | } 32 | 33 | 34 | query CurrSmallGroups( $offset: Int!, $search: String!, $now: date!) { 35 | small_groups( offset: $offset, order_by: [{title: asc}], where: { 36 | _or: [ 37 | {title: {_ilike: $search}}, 38 | ], 39 | semester: {start_date: {_lte: $now}, end_date: {_gte: $now}} 40 | 41 | }) { ...SmallGroupAssociationInfo } 42 | } 43 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/authorization_for.graphql: -------------------------------------------------------------------------------- 1 | # GraphQL query to check if a given user can view draft meetings on a given date (which should generally be now). 2 | query AuthorizationFor($now: date!, $user_id: uuid!) { 3 | # Coordinators can view drafts 4 | current_semesters: semesters(where: { 5 | start_date: {_lte: $now}, 6 | end_date: {_gte: $now} 7 | }) { 8 | # Check if the user is a coordinator. 9 | enrollments(where: {user_id: {_eq: $user_id}}, limit: 1) { 10 | is_coordinator 11 | } 12 | 13 | # Check if the user is a mentor 14 | small_groups(where: {small_group_mentors: {user_id: {_eq: $user_id}}}) { 15 | small_group_id 16 | } 17 | } 18 | 19 | # So can anyone who is a Faculty Advisor 20 | users_by_pk(id: $user_id) { 21 | role 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/creation/context.graphql: -------------------------------------------------------------------------------- 1 | query CreationContext($host: [uuid!]!, $today: date!, $include_semesters: [String!]!) { 2 | # Semesters in which a meeting can be created. This will be 3 | # all current and future semesters in ascending order. 4 | available_semesters: semesters( 5 | where: {_or: [ 6 | {end_date: {_gte: $today}}, 7 | {semester_id: {_in: $include_semesters}} 8 | ]}, 9 | order_by: {start_date: asc} 10 | ) { 11 | semester_id 12 | title 13 | start_date 14 | end_date 15 | } 16 | 17 | # We get around the possibity of no host by letting the rust side make the filter. 18 | # Setting _is_null to true when there's no host, or specifying otherwise. 19 | host: users(where: {id: {_in: $host}}, limit: 1) { 20 | id 21 | first_name 22 | last_name 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/creation/create.graphql: -------------------------------------------------------------------------------- 1 | # Mutation to add a meeting to the RCOS database 2 | mutation CreateMeeting( 3 | $host: uuid, 4 | $title: String, 5 | $start: timestamptz!, 6 | $end: timestamptz!, 7 | $description: String!, 8 | $is_draft: Boolean!, 9 | $is_remote: Boolean!, 10 | $location: String, 11 | $meeting_url: String, 12 | $recording_url: String, 13 | $external_slides_url: String, 14 | $semester_id: String!, 15 | $kind: meeting_type! 16 | ) { 17 | insert_meetings_one(object: { 18 | host_user_id: $host, 19 | title: $title, 20 | start_date_time: $start, 21 | end_date_time: $end, 22 | description: $description, 23 | is_draft: $is_draft, 24 | is_remote: $is_remote, 25 | location: $location, 26 | meeting_url: $meeting_url, 27 | recording_url: $recording_url, 28 | external_presentation_url: $external_slides_url, 29 | semester_id: $semester_id, 30 | type: $kind, 31 | }) { 32 | meeting_id 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/delete.graphql: -------------------------------------------------------------------------------- 1 | # Mutation to delete meetings 2 | mutation DeleteMeeting($meeting_id: Int!) { 3 | # Delete attendances 4 | delete_meeting_attendances(where: {meeting_id: {_eq: $meeting_id}}) { 5 | affected_rows 6 | } 7 | 8 | # Delete meeting itself 9 | delete_meetings_by_pk(meeting_id: $meeting_id) { 10 | meeting_id 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/edit/edit.graphql: -------------------------------------------------------------------------------- 1 | # Update a meeting record. 2 | mutation EditMeeting( 3 | $meeting_id: Int!, 4 | $title: String, 5 | $start: timestamptz!, 6 | $end: timestamptz!, 7 | $semester_id: String!, 8 | $kind: meeting_type!, 9 | $description: String!, 10 | $is_remote: Boolean!, 11 | $is_draft: Boolean!, 12 | $meeting_url: String, 13 | $location: String, 14 | $recording_url: String, 15 | $external_slides_url: String, 16 | $host: uuid, 17 | ) { 18 | # We have to be explicit as to which columns we set, otherwise we risk 19 | # overwriting an existing value with a null unintentionally. 20 | update_meetings_by_pk(pk_columns: {meeting_id: $meeting_id}, _set: { 21 | title: $title, 22 | start_date_time: $start, 23 | end_date_time: $end, 24 | semester_id: $semester_id, 25 | type: $kind, 26 | description: $description, 27 | is_remote: $is_remote, 28 | is_draft: $is_draft, 29 | meeting_url: $meeting_url, 30 | location: $location, 31 | recording_url: $recording_url, 32 | external_presentation_url: $external_slides_url, 33 | host_user_id: $host, 34 | }) { 35 | meeting_id 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/get.graphql: -------------------------------------------------------------------------------- 1 | # Get all the public meetings between two timestamps 2 | query Meetings($start: timestamptz!, $end: timestamptz!, $include_drafts: Boolean!, $accept_types: [meeting_type!]!) { 3 | meetings( 4 | where: { 5 | # Use this instead of comparison so that if $include_drafts 6 | # is true, we still get finalized meetings as well. 7 | is_draft: {_in: [false, $include_drafts]}, 8 | start_date_time: {_gte: $start, _lt: $end}, 9 | type: {_in: $accept_types} 10 | }, 11 | # Order chronologically 12 | order_by: { 13 | start_date_time: asc 14 | } 15 | ) { 16 | meeting_id 17 | start_date_time 18 | end_date_time 19 | external_presentation_url 20 | title 21 | type 22 | 23 | recording_url 24 | meeting_url 25 | is_remote 26 | 27 | is_draft 28 | 29 | location 30 | 31 | description 32 | 33 | # Get info about the host 34 | host: user { 35 | id 36 | first_name 37 | last_name 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/get_by_id.graphql: -------------------------------------------------------------------------------- 1 | # Get some info about a meeting. 2 | query Meeting($id: Int!) { 3 | meeting: meetings_by_pk(meeting_id: $id) { 4 | meeting_id 5 | start_date_time 6 | end_date_time 7 | title 8 | type 9 | 10 | is_draft 11 | is_remote 12 | meeting_url 13 | recording_url 14 | 15 | external_presentation_url 16 | location 17 | 18 | description 19 | 20 | semester { 21 | semester_id 22 | title 23 | } 24 | 25 | # Info on the host 26 | host: user { 27 | first_name 28 | last_name 29 | id 30 | } 31 | 32 | # Attendance count 33 | attendances: meeting_attendances_aggregate { 34 | aggregate { 35 | count 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /graphql/rcos/meetings/get_host.graphql: -------------------------------------------------------------------------------- 1 | # Get the user ID of a host of a meeting 2 | query MeetingHost($meeting_id: Int!) { 3 | meetings_by_pk(meeting_id: $meeting_id) { 4 | host: user { id } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /graphql/rcos/projects/authorization_for.graphql: -------------------------------------------------------------------------------- 1 | # GraphQL query to check if a given user can view draft projects on a given date (which should generally be now). 2 | query AuthorizationFor($now: date!, $user_id: uuid!) { 3 | current_semesters: semesters(where: { 4 | start_date: {_lte: $now}, 5 | end_date: {_gte: $now} 6 | }) { 7 | # Check if the user is a coordinator. 8 | enrollments(where: {user_id: {_eq: $user_id}}, limit: 1) { 9 | is_coordinator 10 | } 11 | 12 | # Check if the user is a mentor 13 | small_groups(where: {small_group_mentors: {user_id: {_eq: $user_id}}}) { 14 | small_group_id 15 | } 16 | } 17 | 18 | # So can anyone who is a Faculty Advisor 19 | users_by_pk(id: $user_id) { 20 | role 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /graphql/rcos/projects/create.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateProject( 2 | $title: String, 3 | $stack: _varchar = "", 4 | $repository_urls: _url = "", 5 | $homepage_url: String = "", 6 | $description: String = "", 7 | $cover_image_url: String = "") { 8 | 9 | insert_projects_one(object: { 10 | title: $title, 11 | stack: $stack, 12 | repository_urls: $repository_urls, 13 | homepage_url: $homepage_url, 14 | description: $description, 15 | cover_image_url: $cover_image_url 16 | }) { 17 | project_id 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /graphql/rcos/projects/get_by_id.graphql: -------------------------------------------------------------------------------- 1 | query Project($id: Int!) { 2 | project: projects_by_pk(project_id: $id) { 3 | project_id 4 | description 5 | # stack 6 | # repository_urls 7 | cover_image_url 8 | created_at 9 | external_organization_id 10 | homepage_url 11 | title 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/current/coordinators.graphql: -------------------------------------------------------------------------------- 1 | # Get a list of user IDs of coordinators in any semester currently occurring. 2 | query CurrentCoordinators($now: date!) { 3 | current_semesters: semesters(where: { 4 | start_date: {_lte: $now}, 5 | end_date: {_gte: $now} 6 | }) { 7 | coordinators: enrollments(where: {is_coordinator: {_eq: true}}) { 8 | user_id 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/current/info.graphql: -------------------------------------------------------------------------------- 1 | query CurrentSemesters($now: date!) { 2 | semesters(where: {start_date: {_lte: $now}, end_date: {_gte: $now}}) { 3 | start_date 4 | end_date 5 | semester_id 6 | title 7 | project_pitches { 8 | proposal_url 9 | user { 10 | first_name 11 | last_name 12 | id 13 | } 14 | } 15 | meetings { 16 | meeting_id 17 | } 18 | project_pitches_aggregate { 19 | aggregate { 20 | count 21 | } 22 | } 23 | meetings_aggregate { 24 | aggregate { 25 | count 26 | } 27 | } 28 | enrollments_aggregate { 29 | aggregate { 30 | count 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/current/mentors.graphql: -------------------------------------------------------------------------------- 1 | # Get the mentors for any semester currently occuring 2 | query CurrentMentors($now: date!) { 3 | current_semesters: semesters(where: { 4 | start_date: {_lte: $now}, 5 | end_date: {_gte: $now} 6 | }) { 7 | small_groups { 8 | small_group_mentors { 9 | user_id 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/get.graphql: -------------------------------------------------------------------------------- 1 | # Paginated semester querying. 2 | query Semesters($offset: Int!, $limit: Int!) { 3 | # Also get the number of semester records for pagination reasons. 4 | semesters_aggregate { aggregate { count } } 5 | 6 | semesters(offset: $offset, limit: $limit, order_by: [{start_date: desc}]) { 7 | semester_id 8 | title 9 | start_date 10 | end_date 11 | 12 | # Get some stats too 13 | 14 | # Enrollments 15 | enrollments_aggregate { 16 | aggregate { 17 | count 18 | } 19 | } 20 | 21 | # Projects 22 | projects: enrollments_aggregate(distinct_on: [project_id], where: {project_id: {_is_null: false}}) { 23 | aggregate { 24 | count 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/get_by_id.graphql: -------------------------------------------------------------------------------- 1 | # Get semester info by id. 2 | query Semester($id: String!) { 3 | semesters_by_pk(semester_id: $id) { 4 | semester_id 5 | title 6 | start_date 7 | end_date 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/mutations/create.graphql: -------------------------------------------------------------------------------- 1 | # GraphQL mutation to create a semester 2 | mutation CreateSemester($id: String!, $title: String!, $start: date!, $end: date!) { 3 | insert_semesters_one(object: { 4 | semester_id: $id, 5 | title: $title, 6 | start_date: $start, 7 | end_date: $end, 8 | }) { 9 | semester_id 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /graphql/rcos/semesters/mutations/edit.graphql: -------------------------------------------------------------------------------- 1 | # Change Semester Details 2 | mutation EditSemester($semester_id: String!, $set_title: String, $set_start: date, $set_end: date) { 3 | update_semesters_by_pk( 4 | pk_columns: {semester_id: $semester_id}, 5 | _set: {end_date: $set_end, start_date: $set_start, title: $set_title} 6 | ) { 7 | semester_id 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /graphql/rcos/stats/landing_page.graphql: -------------------------------------------------------------------------------- 1 | query LandingPageStatistics($now: date!) { 2 | # Count of students in ongoing semesters. 3 | current_students: enrollments_aggregate( 4 | where: { 5 | semester: { 6 | end_date: {_gte: $now} 7 | start_date: {_lte: $now} 8 | } 9 | }, 10 | distinct_on: [user_id] 11 | ) { 12 | aggregate { 13 | count 14 | } 15 | } 16 | 17 | # Count of projects associated with a small group of an ongoing semester 18 | current_projects: projects_aggregate( 19 | where: {small_group_projects: {small_group: {semester: { 20 | start_date: {_lte: $now} 21 | end_date: {_gte: $now} 22 | }}}} 23 | ) { 24 | aggregate { 25 | count 26 | } 27 | } 28 | 29 | total_students: users_aggregate { 30 | aggregate { 31 | count 32 | } 33 | } 34 | 35 | total_projects: projects_aggregate { 36 | aggregate { 37 | count 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /graphql/rcos/users/accounts/for_user.graphql: -------------------------------------------------------------------------------- 1 | query UserAccounts($user_id: uuid!) { 2 | user_accounts(where: {user_id: {_eq: $user_id}}) { 3 | type 4 | account_id 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /graphql/rcos/users/accounts/link.graphql: -------------------------------------------------------------------------------- 1 | mutation LinkUserAccount($user_id: uuid!, $platform: user_account!, $platform_id: String!) { 2 | insert_user_accounts_one( 3 | object: { 4 | user_id: $user_id, 5 | type: $platform, 6 | account_id: $platform_id 7 | }, 8 | ) { user_id } 9 | } 10 | -------------------------------------------------------------------------------- /graphql/rcos/users/accounts/lookup.graphql: -------------------------------------------------------------------------------- 1 | query AccountLookup($platform: user_account!, $user_id: uuid!) { 2 | user_accounts_by_pk(type: $platform, user_id: $user_id) { 3 | account_id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /graphql/rcos/users/accounts/reverse_lookup.graphql: -------------------------------------------------------------------------------- 1 | query ReverseLookup($platform: user_account!, $id: String!) { 2 | user_accounts(limit: 1, where: {type: {_eq: $platform}, account_id: {_eq: $id}}) { 3 | user_id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /graphql/rcos/users/accounts/unlink.graphql: -------------------------------------------------------------------------------- 1 | mutation UnlinkUserAccount($user_id: uuid!, $platform: user_account!) { 2 | delete_user_accounts_by_pk(user_id: $user_id, type: $platform) { 3 | account_id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /graphql/rcos/users/create_one.graphql: -------------------------------------------------------------------------------- 1 | mutation CreateOneUser( 2 | $first_name: String!, 3 | $last_name: String!, 4 | $role: user_role!, 5 | $platform: user_account!, 6 | $platform_id: String!, 7 | ) { 8 | insert_users_one(object: { 9 | first_name: $first_name 10 | last_name: $last_name, 11 | role: $role, 12 | user_accounts: { 13 | data: [ 14 | {type: $platform, account_id: $platform_id} 15 | ] 16 | } 17 | }) { 18 | id 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /graphql/rcos/users/delete.graphql: -------------------------------------------------------------------------------- 1 | # Deletes a user account and all data associated with it. 2 | 3 | mutation DeleteUser($user_id: uuid!) { 4 | # Delete user hosting history -- This just removes the user as the host of 5 | # any meetings they hosted. 6 | update_meetings(where: {host_user_id: {_eq: $user_id}}, _set: {host_user_id: null}) { 7 | affected_rows 8 | } 9 | 10 | # Delete user mentoring records 11 | delete_small_group_mentors(where: {user_id: {_eq: $user_id}}) { 12 | affected_rows 13 | } 14 | 15 | # Delete links to GitHub/Discord/RPI CAS 16 | delete_user_accounts(where: {user_id: {_eq: $user_id}}) { 17 | affected_rows 18 | } 19 | 20 | # Delete user enrollment history 21 | delete_enrollments(where: {user_id: {_eq: $user_id}}) { 22 | affected_rows 23 | } 24 | 25 | # Delete user account itself 26 | delete_users_by_pk(id: $user_id) { 27 | id 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /graphql/rcos/users/discord_whois.graphql: -------------------------------------------------------------------------------- 1 | query DiscordWhoIs($discord_id: String!) { 2 | user_accounts(where: {account_id: {_eq: $discord_id}, type: {_eq: "discord"}}, limit: 1) { 3 | user: user { 4 | id 5 | first_name 6 | last_name 7 | role 8 | cohort 9 | 10 | # RCS ID info for the user 11 | rcs_id: user_accounts(where: {type: {_eq: "rpi"}} limit: 1) { 12 | account_id 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /graphql/rcos/users/edit_profile.graphql: -------------------------------------------------------------------------------- 1 | # Queries and mutations for editing profile information. 2 | 3 | query EditProfileContext($user_id: uuid!) { 4 | users_by_pk(id: $user_id) { 5 | first_name 6 | last_name 7 | cohort 8 | role 9 | 10 | rcs_id: user_accounts(where: {type: {_eq: "rpi"}}) { 11 | account_id 12 | } 13 | } 14 | } 15 | 16 | mutation SaveProfileEdits($user_id: uuid!, $fname: String!, $lname: String!, $cohort: Int, $role: user_role!) { 17 | update_users_by_pk(pk_columns: {id: $user_id}, _set: {first_name: $fname, last_name: $lname, role: $role, cohort: $cohort}) { 18 | id 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /graphql/rcos/users/enrollments/edit_enrollment.graphql: -------------------------------------------------------------------------------- 1 | mutation EditEnrollment($semester_id: String!, 2 | $user_id: uuid!, 3 | $credits: Int, 4 | $pay: Boolean!, 5 | $coordinator: Boolean!, 6 | $lead: Boolean!, 7 | $mid_grade: Float, 8 | $final_grade: Float, 9 | $project: Int) { 10 | update_enrollments_by_pk( 11 | pk_columns: { 12 | semester_id: $semester_id, 13 | user_id: $user_id 14 | }, 15 | _set: { 16 | credits: $credits, 17 | is_coordinator: $coordinator, 18 | is_for_pay: $pay, 19 | is_project_lead: $lead, 20 | mid_year_grade: $mid_grade, 21 | project_id: $project, 22 | final_grade: $final_grade 23 | }) { 24 | user_id 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /graphql/rcos/users/enrollments/enrollment_by_ids.graphql: -------------------------------------------------------------------------------- 1 | query EnrollmentByIds( 2 | $semester_id: String!, 3 | $user_id: uuid!, 4 | ){ 5 | enrollments_by_pk(semester_id: $semester_id, user_id: $user_id) { 6 | final_grade 7 | credits 8 | is_for_pay 9 | is_coordinator 10 | is_project_lead 11 | mid_year_grade 12 | project_id, 13 | user { 14 | first_name 15 | last_name 16 | } 17 | semester { 18 | title 19 | } 20 | } 21 | projects(where: {_or: {}}) { 22 | project_id 23 | title 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /graphql/rcos/users/enrollments/enrollments_lookup.graphql: -------------------------------------------------------------------------------- 1 | # GraphQL query to lookup enrollment data by semester id. 2 | query EnrollmentsLookup( 3 | $semester_id: String!, 4 | ) { 5 | enrollments( 6 | where:{ 7 | semester_id: {_eq: $semester_id} 8 | } 9 | order_by: {semester_id: asc} 10 | ){ 11 | semester_id, 12 | project_id, 13 | is_project_lead, 14 | is_coordinator, 15 | credits, 16 | is_for_pay, 17 | mid_year_grade, 18 | final_grade, 19 | created_at, 20 | user_id, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /graphql/rcos/users/navbar_authentication.graphql: -------------------------------------------------------------------------------- 1 | # Authentication to determine what tabs on the navbar the user should have access to. 2 | query Authentication($user_id: uuid!, $now: date!) { 3 | users_by_pk(id: $user_id) { 4 | # If a semester ID is returned here they're a current coordinator. 5 | is_current_coordinator: enrollments( 6 | where: { 7 | is_coordinator: {_eq: true}, 8 | semester: {start_date: {_lte: $now}, end_date: {_gte: $now}} 9 | }, 10 | limit: 1 11 | ) { semester_id } 12 | 13 | # If a small group ID is returned here then they're a current mentor. 14 | is_current_mentor: small_group_mentors( 15 | where: {small_group: {semester: {start_date: {_lte: $now}, end_date: {_gte: $now}}}}, 16 | limit: 1 17 | ) { 18 | small_group_id 19 | } 20 | 21 | # Get their role too 22 | role 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /graphql/rcos/users/role_lookup.graphql: -------------------------------------------------------------------------------- 1 | # GraphQL query to lookup a user's role. 2 | query RoleLookup($user_id: uuid!) { 3 | users_by_pk(id: $user_id) { 4 | role 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /proposals/fall-2022.md: -------------------------------------------------------------------------------- 1 | # Telescope Spring 2022 2 | 3 | ## Team 4 | - Paul Oppenheimer 5 | - RPI Email: oppenp@rpi.edu 6 | - 7 | - bepvte@gmail.com 8 | - Samir 9 | - RPI Email: beals@rpi.edu 10 | - 11 | - Wyatt Ross 12 | - RPI Email: rossw2@rpi.edu 13 | - 14 | - Alice Bibaud 15 | - RPI Email: bibaua@rpi.edu 16 | - 17 | 18 | ## Links: 19 | - [repository](https://github.com/rcos/Telescope) 20 | - [website](https://telescope.rcos.io) 21 | 22 | ## Stack: 23 | Telescope is written in [rust](https://www.rust-lang.org/) using the 24 | [actix](https://actix.rs/) framework and the [handlebars](https://handlebarsjs.com/) 25 | templating engine. We also use [bootstrap](https://getbootstrap.com/) for CSS and 26 | a [GraphQL](https://graphql.org/) API as the backend. 27 | 28 | ## Goals 29 | This Spring, my goals for Telescope are to add documentation and make the project more self sustaining, and basic support for RCOS projects. I've listed additional features, but going off of the pace of Telescope so far, the timeline is optimistic. 30 | 31 | ## Timeline 32 | Beginning: 33 | - Project add frontend 34 | - Project view 35 | 36 | October: 37 | - Project frontend polishing 38 | - Admin panel changes 39 | - Possible refactors to make it easier to work on 40 | 41 | November: 42 | - Attendance 43 | 44 | December: 45 | - Project pages 46 | - Project edit form 47 | - Project deletion functionality 48 | 49 | January: 50 | - Attendance backend support 51 | - Small group backend support 52 | 53 | February: 54 | - Attendance frontend support 55 | - Small group creation in coordinator panel 56 | -------------------------------------------------------------------------------- /proposals/spring-2022.md: -------------------------------------------------------------------------------- 1 | # Telescope Spring 2022 2 | 3 | ## Team 4 | - Paul Oppenheimer 5 | - RPI Email: oppenp@rpi.edu 6 | - 7 | - bepvte@gmail.com 8 | - Zheyuan Chen 9 | - RPI Email: chenz19@rpi.edu 10 | - 11 | 12 | ## Links: 13 | - [repository](https://github.com/rcos/Telescope) 14 | - [website](https://telescope.rcos.io) 15 | 16 | ## Stack: 17 | Telescope is written in [rust](https://www.rust-lang.org/) using the 18 | [actix](https://actix.rs/) framework and the [handlebars](https://handlebarsjs.com/) 19 | templating engine. We also use [bootstrap](https://getbootstrap.com/) for CSS and 20 | a [GraphQL](https://graphql.org/) API as the backend. 21 | 22 | ## Goals 23 | This Spring, my goals for Telescope are to add documentation and make the project more self sustaining, and basic support for RCOS projects. I've listed additional features, but going off of the pace of Telescope so far, the timeline is optimistic. 24 | 25 | ## Timeline 26 | Beginning: 27 | - Project add backend 28 | - Bug fixing from this semesters issues with discord 29 | 30 | Mid-February: 31 | - Project frontend polishing 32 | - Admin panel backend 33 | 34 | End of February: 35 | - Attendance 36 | - Documentation revamp 37 | 38 | March: 39 | - Project pages 40 | - Project edit form 41 | - Project deletion functionality 42 | 43 | April: 44 | - Attendance backend support 45 | - Small group backend support 46 | 47 | May: 48 | - Attendance frontend support 49 | - Small group creation in coordinator panel 50 | -------------------------------------------------------------------------------- /rcos-data/.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://xxxxxx@xxxxx:xxxxx/xxxxx 2 | RCOS_DB_URI=xxxxxxx 3 | RCOS_JWT_SECRET=REPLACE ME WITH SOMETHING 32+ CHARACTERS OR IT WILL SILENTLY FAIL 4 | RCOS_ADMIN_SECRET=xxxxxx 5 | -------------------------------------------------------------------------------- /rcos-data/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 RCOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /rcos-data/README.md: -------------------------------------------------------------------------------- 1 | # RCOS Database 2 | 3 | > RCOS database schema and related tools. 4 | 5 | ## Overview 6 | 7 | This repo holds the SQL code for setting up the RCOS database, the database 8 | definition, views and triggers, and tools for importing RCOS data from external 9 | platforms like Submitty and Venue. 10 | 11 | ## Database 12 | 13 | The RCOS database is a Postgres DB running on our own infrastructure. Access is 14 | restricted to coordinators and faculty advisors, but the schema and tools used 15 | are open-sourced here. 16 | 17 | ## API 18 | 19 | This database is served by a Hasura GraphQL APIs that 20 | allows access to some resources when unauthenticated for public access, and 21 | allows full access to all resources when authenticated. RCOS infrastructure 22 | cannot connect to the database directly and must interact through this API. 23 | 24 | ## Migrations 25 | 26 | Migrations and metadata are managed using the [Hasura CLI](https://hasura.io/docs/1.0/graphql/core/hasura-cli/index.html). 27 | 28 | ### Running Hasura 29 | 30 | ``` 31 | hasura console --admin-secret xxxxxxxxxxxxxxxxxxxxxxxx 32 | ``` 33 | 34 | ## Deployment 35 | 36 | 1. Write a `.env` file with appropriate values for each key matching the ones used in the `docker-compose.yml` file. 37 | 2. Run `docker-compose up -d` to start everything. 38 | 3. While running, use `docker logs` to inspect the log output of any of the containers. 39 | -------------------------------------------------------------------------------- /rcos-data/config.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | endpoint: https://gql.rcos.io 3 | metadata_directory: metadata 4 | actions: 5 | kind: synchronous 6 | handler_webhook_baseurl: http://localhost:3000 7 | -------------------------------------------------------------------------------- /rcos-data/config/Caddyfile: -------------------------------------------------------------------------------- 1 | { 2 | email rcos-leadership@googlegroups.com 3 | } 4 | 5 | rcos.io { 6 | encode zstd gzip 7 | 8 | reverse_proxy telescope:80 9 | } 10 | 11 | gql.rcos.io { 12 | encode zstd gzip 13 | log { output discard } 14 | 15 | reverse_proxy hasura:8080 16 | } 17 | 18 | telescope.rcos.io, www.rcos.io { 19 | redir https://rcos.io{uri} permanent 20 | } 21 | -------------------------------------------------------------------------------- /rcos-data/config/telescope-config.toml: -------------------------------------------------------------------------------- 1 | # See https://github.com/rcos/Telescope/blob/master/config_example.toml 2 | 3 | log_level = "warn,telescope=info,actix_server=info,actix_web=info,actix=info,reqwest=info" 4 | api_url = "http://hasura:8080/v1/graphql" 5 | telescope_url = "https://rcos.io" 6 | 7 | # RCOS JWT Secret 8 | jwt_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 9 | 10 | # GitHub credentials -- See the Telescope application owned by the RCOS organization on GitHub. 11 | [github_credentials] 12 | client_id = "xxxxxxxxxxxxxxxxxxxx" 13 | client_secret = "****************************************" 14 | 15 | # Discord credentials -- See the Telescope application owned by the RCOS team on Discord. 16 | [discord_config] 17 | client_id = "xxxxxxxxxxxxxxxxxxxx" 18 | client_secret = "****************************************" 19 | bot_token = "xxxxxxxxxxxxxxxxxxxxxxxx.xxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxx" 20 | rcos_guild_id = "xxxxxxxxxxxxxxxxxx" 21 | -------------------------------------------------------------------------------- /rcos-data/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | caddy: 4 | image: caddy:2 5 | restart: unless-stopped 6 | volumes: 7 | - /home/rcos/config/Caddyfile:/etc/caddy/Caddyfile 8 | - caddy_data:/data 9 | ports: 10 | - "80:80" 11 | - "443:443" 12 | 13 | hasura: 14 | image: hasura/graphql-engine:v2.11.1 15 | restart: unless-stopped 16 | environment: 17 | # https://hasura.io/docs/latest/deployment/graphql-engine-flags/reference 18 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "web_anon" 19 | HASURA_GRAPHQL_DATABASE_URL: "${DATABASE_URL}" 20 | HASURA_GRAPHQL_DEV_MODE: "false" 21 | HASURA_GRAPHQL_ENABLE_CONSOLE: "false" 22 | HASURA_GRAPHQL_JWT_SECRET: "{ \"type\": \"HS256\", \"key\": \"${RCOS_JWT_SECRET}\" }" 23 | HASURA_GRAPHQL_LOG_LEVEL: "warn" 24 | HASURA_GRAPHQL_ADMIN_SECRET: "${RCOS_ADMIN_SECRET}" 25 | 26 | telescope: 27 | image: docker.pkg.github.com/rcos/telescope/telescope:0.9.0 28 | restart: unless-stopped 29 | depends_on: 30 | - hasura 31 | volumes: 32 | # The redacted telescope config file is included in this repo. 33 | # Full dockumentation of all of the options is available at 34 | # https://github.com/rcos/Telescope/blob/master/config_example.toml. 35 | - "/home/rcos/config/telescope-config.toml:/telescope/config.toml" 36 | 37 | volumes: 38 | caddy_data: 39 | 40 | -------------------------------------------------------------------------------- /rcos-data/example_queries/current_semester_projects.gql: -------------------------------------------------------------------------------- 1 | query CurrentSemesterProjects($withEnrollments: Boolean!) { 2 | projects(order_by: {title: asc}, where: {enrollments: {semester: {_and: [{start_date: {_lte: "now()"}}, {end_date: {_gte: "now()"}}]}}}) { 3 | project_id 4 | title 5 | enrollments(where: {semester: {_and: [{start_date: {_lte: "now()"}}, {end_date: {_gte: "now()"}}]}}) @include(if: $withEnrollments) { 6 | user { 7 | id 8 | role 9 | first_name 10 | last_name 11 | cohort 12 | } 13 | is_project_lead 14 | credits 15 | is_for_pay 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /rcos-data/example_queries/enrolled_and_no_project.gql: -------------------------------------------------------------------------------- 1 | query enrolled_and_no_project($semester_id: String!) { 2 | enrollments( 3 | where: { semester_id: { _eq: $semester_id }, _not: { project: {} } } # _not : {project: {}} works because project: {} is true for all non-null projects 4 | ) { 5 | user { 6 | id 7 | first_name 8 | last_name 9 | } 10 | credits 11 | is_for_pay 12 | is_coordinator 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /rcos-data/example_queries/semester_coordinators.gql: -------------------------------------------------------------------------------- 1 | query semesters_coordinators($semester_id: String!) { 2 | coordinators: enrollments( 3 | where: { semester_id: { _eq: $semester_id }, is_coordinator: { _eq: true } } 4 | ) { 5 | user { 6 | id 7 | first_name 8 | last_name 9 | role 10 | cohort 11 | } 12 | credits 13 | is_coordinator 14 | is_for_pay 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rcos-data/example_queries/semester_counts.gql: -------------------------------------------------------------------------------- 1 | query semester_counts($semester_id: String!) { 2 | no_credit_aggregate: enrollments_aggregate( 3 | where: { semester_id: { _eq: $semester_id }, credits: { _eq: 0 } } 4 | ) { 5 | aggregate { 6 | count 7 | } 8 | } 9 | for_credit_aggregate: enrollments_aggregate( 10 | where: { semester_id: { _eq: $semester_id }, credits: { _gt: 0 } } 11 | ) { 12 | aggregate { 13 | count 14 | } 15 | } 16 | projects_aggregate( 17 | where: { enrollments: { semester_id: { _eq: $semester_id } } } 18 | ) { 19 | aggregate { 20 | count 21 | } 22 | } 23 | mentors_aggregate: small_group_mentors_aggregate( 24 | where: { small_group: { semester_id: { _eq: $semester_id } } } 25 | ) { 26 | aggregate { 27 | count 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rcos-data/example_queries/semester_enrollments.gql: -------------------------------------------------------------------------------- 1 | query semester_enrollments($semester_id: String!) { 2 | semester: semesters_by_pk(semester_id: $semester_id) { 3 | title 4 | start_date 5 | end_date 6 | 7 | enrollments { 8 | user { 9 | id 10 | first_name 11 | last_name 12 | role 13 | } 14 | credits 15 | is_for_pay 16 | is_project_lead 17 | project { 18 | project_id 19 | title 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rcos-data/example_queries/semester_projects.gql: -------------------------------------------------------------------------------- 1 | query SemesterProjects($semesterIdOrTitle: String!, $withEnrollments: Boolean!) { 2 | projects(order_by: {title: asc}, where: {enrollments: {_or: [{semester_id: {_eq: $semesterIdOrTitle}}, {semester: {title: {_ilike: $semesterIdOrTitle}}}]}}) { 3 | project_id 4 | title 5 | enrollments @include(if: $withEnrollments) { 6 | user { 7 | id 8 | first_name 9 | last_name 10 | cohort 11 | } 12 | is_project_lead 13 | credits 14 | is_for_pay 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rcos-data/example_queries/semester_small_groups.gql: -------------------------------------------------------------------------------- 1 | query semester_small_groups($semester_id: String!) { 2 | small_groups(where: { semester_id: { _eq: $semester_id } }) { 3 | small_group_id 4 | title 5 | location 6 | small_group_projects { 7 | project { 8 | project_id 9 | title 10 | enrollments { 11 | user { 12 | id 13 | first_name 14 | last_name 15 | cohort 16 | } 17 | is_coordinator 18 | credits 19 | is_for_pay 20 | is_project_lead 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /rcos-data/example_queries/users_with_enrollments.gql: -------------------------------------------------------------------------------- 1 | query users_with_enrollments { 2 | users { 3 | id 4 | role 5 | first_name 6 | last_name 7 | cohort 8 | role 9 | enrollments { 10 | semester_id 11 | is_coordinator 12 | credits 13 | project { 14 | project_id 15 | title 16 | } 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /rcos-data/metadata/actions.graphql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/rcos-data/metadata/actions.graphql -------------------------------------------------------------------------------- /rcos-data/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: [] 2 | custom_types: 3 | enums: [] 4 | input_objects: [] 5 | objects: [] 6 | scalars: [] 7 | -------------------------------------------------------------------------------- /rcos-data/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/cron_triggers.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/functions.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/network.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/query_collections.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/remote_schemas.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /rcos-data/metadata/version.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1610903455000_setup/down.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/rcos-data/migrations/1610903455000_setup/down.sql -------------------------------------------------------------------------------- /rcos-data/migrations/1610903455000_setup/up.sql: -------------------------------------------------------------------------------- 1 | SET TIMEZONE='America/New_york'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610903832000_create_types/down.sql: -------------------------------------------------------------------------------- 1 | DROP TYPE user_role; 2 | DROP TYPE user_account; 3 | DROP TYPE meeting_type; 4 | DROP TYPE chat_association_source; 5 | DROP TYPE chat_association_target; 6 | DROP DOMAIN url; -------------------------------------------------------------------------------- /rcos-data/migrations/1610903832000_create_types/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TYPE user_role AS ENUM ( 2 | 'student', 3 | 'faculty', 4 | 'faculty_advisor', 5 | 'alumn', 6 | 'external', 7 | 'external_mentor' 8 | ); 9 | 10 | COMMENT ON TYPE user_role IS 'The user''s position within RCOS'; 11 | 12 | CREATE TYPE user_account AS ENUM ( 13 | 'rpi', 14 | 'discord', 15 | 'mattermost', 16 | 'github', 17 | 'gitlab', 18 | 'bitbucket' 19 | ); 20 | 21 | COMMENT ON TYPE user_account IS 'The website this account is for'; 22 | 23 | CREATE TYPE meeting_type AS ENUM ( 24 | 'large_group', 25 | 'small_group', 26 | 'presentations', 27 | 'bonus_session', 28 | 'grading', 29 | 'mentors', 30 | 'coordinators', 31 | 'other' 32 | ); 33 | 34 | COMMENT ON TYPE meeting_type IS 'The kind of RCOS meeting this was'; 35 | 36 | CREATE TYPE chat_association_source AS ENUM ( 37 | 'project', 38 | 'small_group' 39 | ); 40 | 41 | COMMENT ON TYPE chat_association_source IS 'The kind of group this chat is for'; 42 | 43 | CREATE TYPE chat_association_target AS ENUM ( 44 | 'discord_server', 45 | 'discord_text_channel', 46 | 'discord_voice_channel', 47 | 'discord_category', 48 | 'discord_role' 49 | ); 50 | 51 | COMMENT ON TYPE chat_association_target IS 'The kind of chat that this refers to'; 52 | 53 | -- https://www.cybertec-postgresql.com/en/postgresql-useful-new-data-types/ 54 | CREATE DOMAIN url AS TEXT 55 | CHECK (VALUE ~ 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#()?&//=]*)'); 56 | 57 | COMMENT ON DOMAIN url IS 'Type that match URLs (http or https)'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610904400000_create_semesters_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE semesters; -------------------------------------------------------------------------------- /rcos-data/migrations/1610904400000_create_semesters_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE semesters ( 2 | -- Semester ID must be 6 numbers. 3 | -- This assumes all semesters are after the year 999 and before 10,000 4 | semester_id VARCHAR(6) PRIMARY KEY CHECK(semester_id ~ '^\d{6}$'), 5 | title VARCHAR NOT NULL, 6 | start_date DATE NOT NULL, 7 | end_date DATE NOT NULL CHECK (end_date > start_date) 8 | ); 9 | 10 | CREATE INDEX ON semesters (start_date, end_date); 11 | 12 | COMMENT ON TABLE semesters IS 'Dates are from official academic calendar: 13 | https://info.rpi.edu/registrar/academic-calendar 14 | A school year has 3 semesters, Spring, Summer, and Fall. Semester IDs are 15 | 4-digit starting year + 2-digit start month, e.g. 202009'; 16 | COMMENT ON COLUMN semesters.title IS 'Typically season and year, e.g. Fall 2020'; 17 | COMMENT ON COLUMN semesters.start_date IS 'Date that classes start'; 18 | COMMENT ON COLUMN semesters.end_date IS 'Date that semester ends'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906701000_create_announcements_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE announcements; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906701000_create_announcements_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE announcements ( 2 | announcement_id SERIAL PRIMARY KEY, 3 | semester_id varchar NOT NULL REFERENCES semesters(semester_id), 4 | title varchar NOT NULL, 5 | body_markdown text NOT NULL, 6 | close_date_time TIMESTAMPTZ, 7 | created_at TIMESTAMPTZ NOT NULL DEFAULT now() 8 | ); 9 | 10 | COMMENT ON TABLE announcements IS 'Various announcements made by RCOS'; 11 | COMMENT ON COLUMN announcements.title IS 'Short title of announcement'; 12 | COMMENT ON COLUMN announcements.body_markdown IS 'Markdown-supported announcement content'; 13 | COMMENT ON COLUMN announcements.close_date_time IS 'Date and time the announcement ends'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906743000_create_users_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906743000_create_users_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | username VARCHAR PRIMARY KEY, 3 | preferred_name VARCHAR, 4 | first_name VARCHAR NOT NULL, 5 | last_name VARCHAR NOT NULL, 6 | cohort INT, 7 | role user_role NOT NULL, 8 | timezone text NOT NULL DEFAULT 'America/New_York', 9 | created_at TIMESTAMPTZ NOT NULL DEFAULT now() 10 | ); 11 | 12 | COMMENT ON TABLE users IS 'Users can be students, external mentors, and faculty. 13 | Their user details are not dependent on the semester'; 14 | COMMENT ON COLUMN users.preferred_name IS 'Optional preferred first name to use in UIs'; 15 | COMMENT ON COLUMN users.first_name IS 'Given name of user'; 16 | COMMENT ON COLUMN users.last_name IS 'Family name of user'; 17 | COMMENT ON COLUMN users.cohort IS 'Entry year (only set for students)'; 18 | COMMENT ON COLUMN users.role IS 'Role of user in RCOS, determines permissions'; 19 | COMMENT ON COLUMN users.timezone IS 'Timezone from TZ list'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906899000_create_user_accounts_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE user_accounts; -------------------------------------------------------------------------------- /rcos-data/migrations/1610906899000_create_user_accounts_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE user_accounts ( 2 | username VARCHAR REFERENCES users (username), 3 | type user_account, 4 | account_id VARCHAR NOT NULL, 5 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 6 | 7 | PRIMARY KEY (username, type) 8 | ); 9 | 10 | COMMENT ON TABLE user_accounts IS 'User accounts such as Discord, GitHub, GitLab, etc.'; 11 | COMMENT ON COLUMN user_accounts.type IS 'Type of external account that is connected'; 12 | COMMENT ON COLUMN user_accounts.account_id IS 'Unique ID/username of account'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610907783000_create_projects_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE projects; -------------------------------------------------------------------------------- /rcos-data/migrations/1610907783000_create_projects_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE projects ( 2 | project_id SERIAL PRIMARY KEY, 3 | title VARCHAR UNIQUE NOT NULL, 4 | description TEXT NOT NULL, 5 | languages VARCHAR[] NOT NULL DEFAULT '{}', 6 | stack VARCHAR[] NOT NULL DEFAULT '{}', 7 | cover_image_url url, 8 | homepage_url url, 9 | repository_urls url[] NOT NULL, 10 | created_at TIMESTAMPTZ NOT NULL DEFAULT now() 11 | ); 12 | 13 | -- TODO indexes for searching 14 | 15 | COMMENT ON TABLE projects IS 'Project details are not semester dependent'; 16 | COMMENT ON COLUMN projects.languages IS 'List of languages used, all lowercase'; 17 | COMMENT ON COLUMN projects.stack IS 'List of technologies used'; 18 | COMMENT ON COLUMN projects.cover_image_url IS 'URL to logo image'; 19 | COMMENT ON COLUMN projects.homepage_url IS 'Optional link to project homepage'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610907975000_create_project_presentations_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE project_presentations; -------------------------------------------------------------------------------- /rcos-data/migrations/1610907975000_create_project_presentations_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE project_presentations ( 2 | project_id INT NOT NULL REFERENCES projects (project_id), 3 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 4 | presentation_url url NOT NULL, 5 | is_draft boolean NOT NULL DEFAULT true, 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 7 | 8 | PRIMARY KEY (project_id, semester_id) 9 | ); 10 | 11 | COMMENT ON TABLE project_presentations IS 'Presentations given by RCOS projects'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908265000_create_enrollments_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE enrollments; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908265000_create_enrollments_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE enrollments ( 2 | semester_id VARCHAR REFERENCES semesters (semester_id), 3 | username VARCHAR REFERENCES users (username), 4 | project_id INT REFERENCES projects (project_id), 5 | is_project_lead BOOLEAN DEFAULT false, 6 | is_coordinator BOOLEAN DEFAULT false, 7 | credits INT NOT NULL DEFAULT 0, 8 | is_for_pay BOOLEAN DEFAULT false, 9 | mid_year_grade REAL CHECK (mid_year_grade >= 0.0), 10 | final_grade REAL CHECK (final_grade >= 0.0), 11 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 12 | 13 | PRIMARY KEY (semester_id, username) 14 | ); 15 | 16 | CREATE INDEX ON enrollments (project_id); 17 | CREATE INDEX ON enrollments (credits) WHERE credits > 0; 18 | 19 | COMMENT ON TABLE enrollments IS 'An enrollment of a user in RCOS for a specific 20 | semester. They might or might not be on a project and might or might not be 21 | taking RCOS for credit.'; 22 | COMMENT ON COLUMN enrollments.is_project_lead IS 'Allows multiple project leads'; 23 | COMMENT ON COLUMN enrollments.credits IS '0-4 where 0 means just for experience'; 24 | COMMENT ON COLUMN enrollments.is_for_pay IS 'True if taking RCOS for pay'; 25 | COMMENT ON COLUMN enrollments.mid_year_grade IS '0.0-100.0'; 26 | COMMENT ON COLUMN enrollments.final_grade IS '0.0-100.0'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908266000_create_mentor_proposals_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE mentor_proposals; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908266000_create_mentor_proposals_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE mentor_proposals ( 2 | semester_id VARCHAR, 3 | username VARCHAR, 4 | reason TEXT NOT NULL, 5 | skillset TEXT NOT NULL, 6 | reviewer_username VARCHAR, 7 | reviewer_comments TEXT, 8 | is_approved boolean DEFAULT false, 9 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 10 | 11 | PRIMARY KEY (semester_id, username), 12 | 13 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username), 14 | FOREIGN KEY (semester_id, reviewer_username) REFERENCES enrollments (semester_id, username) 15 | ); 16 | 17 | COMMENT ON TABLE mentor_proposals IS 'Users Interested in mentoring each 18 | semester must submit a proposal and be approved'; 19 | COMMENT ON COLUMN mentor_proposals.username IS 'Username of mentor to-be'; 20 | COMMENT ON COLUMN mentor_proposals.reason IS 'The reason the user would like to mentor'; 21 | COMMENT ON COLUMN mentor_proposals.skillset IS 'Short details of technologies 22 | user can mentor for'; 23 | COMMENT ON COLUMN mentor_proposals.reviewer_username IS 'Username of 24 | coordinator/faculty who reviewed proposal'; 25 | COMMENT ON COLUMN mentor_proposals.reviewer_comments IS 'Optional comments left by reviewer'; 26 | COMMENT ON COLUMN mentor_proposals.is_approved IS 'True if user was approved to 27 | become a mentor for the semester'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908267000_create_workshop_proposals_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE workshop_proposals; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908267000_create_workshop_proposals_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE workshop_proposals ( 2 | workshop_proposal_id SERIAL PRIMARY KEY, 3 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 4 | username VARCHAR NOT NULL REFERENCES users (username), 5 | topic VARCHAR NOT NULL, 6 | title VARCHAR NOT NULL, 7 | qualifications VARCHAR NOT NULL, 8 | first_choice_at TIMESTAMPTZ NOT NULL CHECK (first_choice_at > now()), 9 | second_choice_at TIMESTAMPTZ NOT NULL CHECK (second_choice_at > now()), 10 | third_choice_at TIMESTAMPTZ NOT NULL CHECK (third_choice_at > now()), 11 | reviewer_username VARCHAR REFERENCES users (username), 12 | reviewer_comments TEXT, 13 | is_approved BOOLEAN DEFAULT false, 14 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 15 | 16 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username), 17 | FOREIGN KEY (semester_id, reviewer_username) REFERENCES enrollments (semester_id, username) 18 | ); 19 | 20 | CREATE INDEX ON workshop_proposals (semester_id); 21 | CREATE INDEX ON workshop_proposals (username); 22 | 23 | COMMENT ON TABLE workshop_proposals IS 'Users (typically mentors) must submit a 24 | proposal to host a workshop and be approved'; 25 | COMMENT ON COLUMN workshop_proposals.first_choice_at IS 'First choice for date 26 | and time to host workshop'; 27 | COMMENT ON COLUMN workshop_proposals.second_choice_at IS 'Second choice for date 28 | and time to host workshop'; 29 | COMMENT ON COLUMN workshop_proposals.third_choice_at IS 'Third choice for date 30 | and time to host workshop'; 31 | COMMENT ON COLUMN workshop_proposals.reviewer_username IS 'Username of 32 | coordinator/faculty who reviewed proposal'; 33 | COMMENT ON COLUMN workshop_proposals.reviewer_comments IS 'Optional comments left by reviewer'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908268000_create_pay_requests_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE pay_requests; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908268000_create_pay_requests_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE pay_requests ( 2 | semester_id VARCHAR NOT NULL, 3 | username VARCHAR NOT NULL, 4 | reason TEXT NOT NULL, 5 | is_approved BOOLEAN DEFAULT false, 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 7 | 8 | PRIMARY KEY (semester_id, username), 9 | 10 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username) 11 | ); 12 | 13 | COMMENT ON TABLE pay_requests IS 'Users can request to take RCOS for pay INSTEAD 14 | of credit and must be approved'; 15 | COMMENT ON COLUMN pay_requests.reason IS 'The justification for being paid'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908269000_create_project_pitches_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE project_pitches; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908269000_create_project_pitches_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE project_pitches ( 2 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 3 | username VARCHAR NOT NULL REFERENCES users (username), 4 | existing_project_id INT REFERENCES projects (project_id), 5 | proposed_title VARCHAR, 6 | proposed_description TEXT, 7 | proposed_stack TEXT, 8 | pitch_slide_url url, 9 | proposal_url url, 10 | is_looking_for_members BOOLEAN NOT NULL DEFAULT true, 11 | is_approved boolean NOT NULL DEFAULT false, 12 | reviewer_username VARCHAR REFERENCES users (username), 13 | reviewer_comments TEXT, 14 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 15 | 16 | PRIMARY KEY (semester_id, username), 17 | 18 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username), 19 | FOREIGN KEY (semester_id, reviewer_username) REFERENCES enrollments (semester_id, username) 20 | ); 21 | 22 | COMMENT ON TABLE project_pitches IS 'Represents a project pitch by a member at 23 | the start of a semester. If the pitch is for an existing project, the title, 24 | description, stack can be grabbed. Otherwise, when the proposal is approved 25 | those fields are used to create the actual project'; 26 | COMMENT ON COLUMN project_pitches.existing_project_id IS 'Only if pitch for 27 | existing RCOS project'; 28 | COMMENT ON COLUMN project_pitches.proposed_title IS 'Null if for existing RCOS project'; 29 | COMMENT ON COLUMN project_pitches.pitch_slide_url IS 'Link to 1-slide 30 | presentation for pitch (if they are open)'; 31 | COMMENT ON COLUMN project_pitches.proposal_url IS 'Link to semester project proposal'; 32 | COMMENT ON COLUMN project_pitches.is_looking_for_members IS 'Open to new members?'; 33 | COMMENT ON COLUMN project_pitches.reviewer_comments IS 'Optional notes from graders'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908437000_create_small_groups_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE small_groups; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908437000_create_small_groups_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE small_groups ( 2 | small_group_id SERIAL PRIMARY KEY, 3 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 4 | title VARCHAR NOT NULL, 5 | location VARCHAR 6 | ); 7 | 8 | CREATE INDEX ON small_groups (semester_id); 9 | CREATE UNIQUE INDEX ON small_groups (semester_id, title); 10 | 11 | COMMENT ON TABLE small_groups IS 'A small group for a specific semester. There 12 | will likely be repeats over semesters only differentiated by semester id.'; 13 | COMMENT ON COLUMN small_groups.title IS 'The title of the small group.'; 14 | COMMENT ON COLUMN small_groups.location IS 'Possible physical location of small 15 | group, i.e. building and room'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908550000_create_small_group_projects_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE small_group_projects; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908550000_create_small_group_projects_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE small_group_projects ( 2 | small_group_id INT NOT NULL REFERENCES small_groups (small_group_id), 3 | project_id INT NOT NULL REFERENCES projects (project_id), 4 | 5 | PRIMARY KEY (small_group_id, project_id) 6 | ); 7 | 8 | COMMENT ON TABLE small_group_projects IS 'Relation between small groups and 9 | projects'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908717000_create_small_group_mentors_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE small_group_mentors; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908717000_create_small_group_mentors_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE small_group_mentors ( 2 | small_group_id INT NOT NULL REFERENCES small_groups (small_group_id), 3 | username VARCHAR NOT NULL REFERENCES users (username), 4 | 5 | PRIMARY KEY (small_group_id, username) 6 | ); 7 | 8 | COMMENT ON TABLE small_group_mentors IS 'Relation between small groups and 9 | users who are the group''s mentors'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908840000_create_status_update_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE status_updates; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908840000_create_status_update_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE status_updates ( 2 | status_update_id SERIAL PRIMARY KEY, 3 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 4 | title VARCHAR, 5 | open_date_time TIMESTAMPTZ NOT NULL, 6 | close_date_time TIMESTAMPTZ CHECK ((close_date_time IS NULL) OR (close_date_time > open_date_time)), 7 | created_at TIMESTAMPTZ NOT NULL DEFAULT now() 8 | ); 9 | 10 | COMMENT ON COLUMN status_updates.title IS 'Optional title. If not set, can use open_at date'; 11 | COMMENT ON COLUMN status_updates.open_date_time IS 'When submissions start to be accepted'; 12 | COMMENT ON COLUMN status_updates.close_date_time IS 'When submissions stop being submittable'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908902000_create_status_update_submissions_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE status_update_submissions; -------------------------------------------------------------------------------- /rcos-data/migrations/1610908902000_create_status_update_submissions_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE status_update_submissions ( 2 | status_update_id INT NOT NULL REFERENCES status_updates (status_update_id), 3 | username VARCHAR NOT NULL REFERENCES users (username), 4 | this_week TEXT NOT NULL, 5 | next_week TEXT NOT NULL, 6 | blockers TEXT NOT NULL, 7 | grade REAL CHECK (grade >= 0.0), 8 | grader_username VARCHAR REFERENCES users (username), 9 | grader_comments TEXT, 10 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 11 | 12 | PRIMARY KEY (status_update_id, username) 13 | ); 14 | 15 | COMMENT ON TABLE status_update_submissions IS 'A status update submission by a enrolled member'; 16 | COMMENT ON COLUMN status_update_submissions.grade IS 'Scale from 0-1: did this 17 | status update meet the requirements'; 18 | COMMENT ON COLUMN status_update_submissions.grader_username IS 'The 19 | mentor/coordinator/faculty member that graded this status_update'; 20 | COMMENT ON COLUMN status_update_submissions.grader_comments IS 'Given by grader'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909370000_create_meetings_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE meetings; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909519000_create_meeting_attendances_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE meeting_attendances; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909519000_create_meeting_attendances_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE meeting_attendances ( 2 | meeting_id INT NOT NULL REFERENCES meetings (meeting_id), 3 | username VARCHAR NOT NULL REFERENCES users (username), 4 | is_manually_added BOOLEAN DEFAULT false, 5 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 6 | 7 | PRIMARY KEY (meeting_id, username) 8 | ); 9 | 10 | COMMENT ON COLUMN meeting_attendances.is_manually_added IS 'True if manually 11 | added by admin and not user'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909635000_create_bonus_attendances_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE bonus_attendances; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909635000_create_bonus_attendances_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE bonus_attendances ( 2 | bonus_attendance_id SERIAL PRIMARY KEY, 3 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 4 | username VARCHAR NOT NULL REFERENCES users (username), 5 | reason TEXT, 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 7 | 8 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username) 9 | ); 10 | 11 | CREATE INDEX ON bonus_attendances (semester_id, username); 12 | 13 | COMMENT ON TABLE bonus_attendances IS 'Bonus attendances from different events'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909920000_create_project_presentation_grades_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE project_presentation_grades; -------------------------------------------------------------------------------- /rcos-data/migrations/1610909920000_create_project_presentation_grades_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE project_presentation_grades ( 2 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 3 | project_id INT NOT NULL REFERENCES projects (project_id), 4 | grader_username VARCHAR NOT NULL REFERENCES users (username), 5 | grade REAL NOT NULL CHECK (grade >= 0.0), 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 7 | 8 | PRIMARY KEY (semester_id, project_id, grader_username), 9 | 10 | FOREIGN KEY (semester_id, grader_username) REFERENCES enrollments (semester_id, username) 11 | ); 12 | 13 | COMMENT ON TABLE project_presentation_grades IS 'Grades for end of semester 14 | project presentations. Might need to separate grade Into multiple'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910056000_create_chat_associations_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE chat_associations; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910056000_create_chat_associations_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE chat_associations ( 2 | source_type chat_association_source, 3 | target_type chat_association_target, 4 | source_id VARCHAR, 5 | target_id VARCHAR NOT NULL, 6 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 7 | 8 | PRIMARY KEY (source_type, target_type, source_id) 9 | ); 10 | 11 | COMMENT ON TABLE chat_associations IS 'Association of chat platform channel or 12 | other entity with a small group or project'; 13 | COMMENT ON COLUMN chat_associations.source_type IS 'What the target is 14 | associated with, e.g. project or small group'; 15 | COMMENT ON COLUMN chat_associations.target_type IS 'What the source is 16 | associated with, e.g. Discord TEXT channel'; 17 | COMMENT ON COLUMN chat_associations.source_id IS 'ID of source, e.g. project id 18 | or small group id'; 19 | COMMENT ON COLUMN chat_associations.target_id IS 'ID of target on platform, e.g. 20 | Discord channel ID'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910121000_create_final_grade_appeal_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE final_grade_appeal; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910121000_create_final_grade_appeal_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE final_grade_appeal ( 2 | semester_id VARCHAR NOT NULL REFERENCES semesters (semester_id), 3 | username VARCHAR NOT NULL REFERENCES users (username), 4 | expected_grade VARCHAR NOT NULL, -- TODO is this supposed to be a letter grade? 5 | reason TEXT NOT NULL, 6 | is_handled BOOLEAN NOT NULL DEFAULT false, 7 | created_at TIMESTAMPTZ NOT NULL DEFAULT now(), 8 | 9 | PRIMARY KEY (semester_id, username), 10 | 11 | FOREIGN KEY (semester_id, username) REFERENCES enrollments (semester_id, username) 12 | ); 13 | 14 | COMMENT ON COLUMN final_grade_appeal.expected_grade IS 'Grade the student 15 | expected to receive'; 16 | COMMENT ON COLUMN final_grade_appeal.reason IS 'Reason the student believes they 17 | deserve expected_grade'; 18 | COMMENT ON COLUMN final_grade_appeal.is_handled IS 'Whether a faculty advisor 19 | has handled this appeal yet'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910395000_create_small_group_members_view/down.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW small_group_members; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910395000_create_small_group_members_view/up.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW small_group_members AS 2 | SELECT sg.small_group_id, 3 | e.* 4 | FROM enrollments AS e 5 | JOIN projects AS p ON p.project_id = e.project_id 6 | JOIN small_group_projects AS sgp ON sgp.project_id = p.project_id 7 | JOIN small_groups AS sg ON sg.small_group_id = sgp.small_group_id; 8 | -- sgid doesn't exist and I couldn't figure out what this WHERE is for 9 | -- WHERE sg.small_group_id = sgid; 10 | 11 | COMMENT ON VIEW small_group_members IS 'View for easy access to small group members'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910455000_create_public_meetings_view/down.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW public_meetings; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910455000_create_public_meetings_view/up.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW public_meetings AS 2 | SELECT * FROM meetings AS m 3 | WHERE m.is_public = TRUE 4 | ORDER BY m.start_date_time; 5 | 6 | COMMENT ON VIEW public_meetings IS 'View for access to public meetings'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910643000_create_faculty_advisors_view/down.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW faculty_advisors; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910643000_create_faculty_advisors_view/up.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW faculty_advisors AS 2 | SELECT u.username, 3 | u.preferred_name, 4 | u.first_name, 5 | u.last_name 6 | FROM users AS u 7 | WHERE u.role = 'faculty_advisor'; 8 | 9 | COMMENT ON VIEW faculty_advisors IS 'View for access to Faculty Advisors'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910650000_create_coordinators_view/down.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW coordinators; -------------------------------------------------------------------------------- /rcos-data/migrations/1610910650000_create_coordinators_view/up.sql: -------------------------------------------------------------------------------- 1 | CREATE VIEW coordinators AS 2 | SELECT DISTINCT e.semester_id, 3 | u.username, 4 | u.preferred_name, 5 | u.first_name, 6 | u.last_name 7 | FROM users AS u 8 | JOIN enrollments AS e ON e.username = u.username 9 | WHERE e.is_coordinator = TRUE 10 | ORDER BY e.semester_id, u.username; 11 | 12 | COMMENT ON VIEW coordinators IS 'View for access to Coordinators each semester'; -------------------------------------------------------------------------------- /rcos-data/migrations/1610912853000_create_roles/down.sql: -------------------------------------------------------------------------------- 1 | -- The order of these must be the reverse of up.sql 2 | DROP OWNED BY authenticator; 3 | DROP OWNED BY api_user; 4 | DROP OWNED BY web_anon; 5 | 6 | DROP ROLE authenticator; 7 | DROP ROLE api_user; 8 | DROP ROLE web_anon; 9 | -------------------------------------------------------------------------------- /rcos-data/migrations/1610912853000_create_roles/up.sql: -------------------------------------------------------------------------------- 1 | -- Create user that unauthenicated API requests will use. By default can't do anything. 2 | CREATE ROLE web_anon NOLOGIN; 3 | GRANT usage ON SCHEMA public TO web_anon; 4 | GRANT SELECT ON public_meetings TO web_anon; -- Can read *public* meetings 5 | GRANT SELECT ON faculty_advisors TO web_anon; -- Can read faculty advisors 6 | GRANT SELECT ON coordinators TO web_anon; -- Can read coordinators 7 | GRANT SELECT ON projects TO web_anon; -- Can read projects 8 | GRANT SELECT ON announcements TO web_anon; -- Can read announcements 9 | 10 | -- Create user that authenticated API requests will use 11 | CREATE ROLE api_user NOLOGIN; 12 | GRANT usage ON SCHEMA public TO api_user; 13 | GRANT ALL ON ALL TABLES IN SCHEMA public TO api_user; 14 | GRANT ALL ON ALL sequences IN SCHEMA public TO api_user; 15 | 16 | -- Create user that can be logged in for the API 17 | CREATE ROLE authenticator NOINHERIT LOGIN PASSWORD 'TESTING_PASSWORD'; -- MAKE SURE TO CHANGE THIS IN PRODUCTION 18 | GRANT web_anon TO authenticator; 19 | -------------------------------------------------------------------------------- /rcos-data/migrations/1611347809000_grant_api_user_to_authenticator/down.sql: -------------------------------------------------------------------------------- 1 | -- Revoke permissions on the api user that postgrest uses 2 | 3 | REVOKE api_user FROM authenticator; -------------------------------------------------------------------------------- /rcos-data/migrations/1611347809000_grant_api_user_to_authenticator/up.sql: -------------------------------------------------------------------------------- 1 | -- Postgrest can change to api_user role for authenticated requests 2 | GRANT api_user TO authenticator; -------------------------------------------------------------------------------- /rcos-data/migrations/1611354573000_simplify_url_domain/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove use of URL type from table's it's in. 2 | ALTER TABLE projects ALTER COLUMN repository_urls TYPE varchar[]; 3 | 4 | -- Re-define original URL type. 5 | ALTER DOMAIN url DROP CONSTRAINT url_check; 6 | ALTER DOMAIN url ADD CONSTRAINT url_check 7 | CHECK (VALUE ~ 'https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#()?&//=]*)'); 8 | 9 | -- Re-define user in projects table 10 | ALTER TABLE projects ALTER COLUMN repository_urls TYPE url[]; 11 | -------------------------------------------------------------------------------- /rcos-data/migrations/1611354573000_simplify_url_domain/up.sql: -------------------------------------------------------------------------------- 1 | -- Remove use of url type in projects table. 2 | ALTER TABLE projects ALTER COLUMN repository_urls TYPE varchar[]; 3 | 4 | -- Simplify definition of url domain. 5 | ALTER DOMAIN url DROP CONSTRAINT url_check; 6 | ALTER DOMAIN url ADD CONSTRAINT url_check 7 | CHECK (VALUE ~ 'https?:\/\/.+'); 8 | 9 | -- Re-define column in projects to use url. 10 | ALTER TABLE projects ALTER COLUMN repository_urls TYPE url[]; 11 | -------------------------------------------------------------------------------- /rcos-data/migrations/1612823016000_add_is_external_project_field/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects 2 | DROP COLUMN is_external CASCADE; -------------------------------------------------------------------------------- /rcos-data/migrations/1612823016000_add_is_external_project_field/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects 2 | ADD COLUMN is_external BOOLEAN NOT NULL DEFAULT FALSE; -------------------------------------------------------------------------------- /rcos-data/migrations/1612823449000_remove_project_languages_field/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects 2 | ADD COLUMN languages VARCHAR[] DEFAULT '{}'::VARCHAR[] NOT NULL; -------------------------------------------------------------------------------- /rcos-data/migrations/1612823449000_remove_project_languages_field/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects 2 | DROP COLUMN languages CASCADE; -------------------------------------------------------------------------------- /rcos-data/migrations/1612831682000_add_external_organizations_table/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE external_organizations; -------------------------------------------------------------------------------- /rcos-data/migrations/1612831682000_add_external_organizations_table/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE external_organizations ( 2 | external_organization_id SERIAL PRIMARY KEY, 3 | title VARCHAR NOT NULL, 4 | homepage url NOT NULL, 5 | contact_emails url[] NOT NULL DEFAULT '{}'::url[] 6 | ) -------------------------------------------------------------------------------- /rcos-data/migrations/1612831911000_add_external_org_key_to_projects/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE projects 2 | ADD COLUMN is_external BOOLEAN NOT NULL DEFAULT FALSE; 3 | 4 | ALTER TABLE projects 5 | DROP COLUMN external_organization_id; -------------------------------------------------------------------------------- /rcos-data/migrations/1612831911000_add_external_org_key_to_projects/up.sql: -------------------------------------------------------------------------------- 1 | -- No need for this flag now, just check if external_organization_id is null 2 | ALTER TABLE projects 3 | DROP COLUMN is_external; 4 | 5 | ALTER TABLE projects 6 | ADD external_organization_id INT REFERENCES external_organizations (external_organization_id); 7 | 8 | COMMENT ON COLUMN projects.external_organization_id IS 'Optional external org this project belongs to, e.g. IBM' -------------------------------------------------------------------------------- /rcos-data/migrations/1613612582809_create_duplicate_users_view/down.sql: -------------------------------------------------------------------------------- 1 | DROP VIEW duplicate_users; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1613612582809_create_duplicate_users_view/up.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE VIEW duplicate_users 2 | AS 3 | SELECT DISTINCT 4 | LEAST(a.username, b.username) AS username_a, 5 | GREATEST(a.username, b.username) AS username_b, 6 | a.first_name, 7 | a.last_name 8 | FROM users AS a 9 | JOIN users AS b 10 | ON a.first_name ILIKE b.first_name 11 | AND a.last_name ILIKE b.last_name 12 | AND a.username != b.username; 13 | -------------------------------------------------------------------------------- /rcos-data/migrations/1614142201290_remove_unauthenticated_roles/down.sql: -------------------------------------------------------------------------------- 1 | -- Create user that authenticated API requests will use 2 | CREATE ROLE api_user NOLOGIN; 3 | GRANT usage ON SCHEMA public TO api_user; 4 | GRANT ALL ON ALL TABLES IN SCHEMA public TO api_user; 5 | GRANT ALL ON ALL sequences IN SCHEMA public TO api_user; 6 | 7 | -- Create user that can be logged in for the API 8 | CREATE ROLE authenticator NOLOGIN; 9 | GRANT web_anon TO authenticator; 10 | GRANT api_user TO authenticator; 11 | 12 | -------------------------------------------------------------------------------- /rcos-data/migrations/1614142201290_remove_unauthenticated_roles/up.sql: -------------------------------------------------------------------------------- 1 | DROP OWNED BY authenticator; 2 | DROP OWNED BY api_user; 3 | 4 | DROP ROLE authenticator; 5 | DROP ROLE api_user; 6 | 7 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615336759471_add_user_ids/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove user id colum from the users table. 2 | 3 | ALTER TABLE users DROP COLUMN id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615336759471_add_user_ids/up.sql: -------------------------------------------------------------------------------- 1 | -- Create user ID column and values. Do not alter the database in any other way. 2 | -- Does not remove usernames. 3 | 4 | -- Add a column to the user's table as a randomly generated UUID. 5 | ALTER TABLE users ADD COLUMN id UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(); 6 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615338200877_alter_user_accounts_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- remove the user id column from the user_accounts table. 2 | 3 | ALTER TABLE user_accounts DROP COLUMN user_id; -------------------------------------------------------------------------------- /rcos-data/migrations/1615338200877_alter_user_accounts_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- This migration updates the foreign key constraint of the user_accounts table 2 | -- to use the id column of the users table. 3 | -- This does not modify any other tables. 4 | 5 | -- Add the user_id column and foreign key constraint 6 | ALTER TABLE user_accounts ADD COLUMN user_id UUID REFERENCES users(id); 7 | 8 | -- Set all user ids in the accounts table to the appropriate value. 9 | UPDATE user_accounts 10 | SET user_id = id 11 | FROM users 12 | WHERE user_accounts.username = users.username; 13 | 14 | -- Constrain user_account's user_ids to not be null. 15 | ALTER TABLE user_accounts ALTER COLUMN user_id SET NOT NULL; 16 | 17 | -- Add unique constraint for user accounts 18 | ALTER TABLE user_accounts 19 | ADD CONSTRAINT user_accounts_unique_user_id_type 20 | UNIQUE (user_id, type); 21 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615344082163_alter_bonus_attendances_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop user id from bonus_attendances 2 | ALTER TABLE bonus_attendances DROP COLUMN user_id; 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615344082163_alter_bonus_attendances_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user_id foreign key to bonus_attendances table 2 | 3 | -- Add user_id column and constraint to the bonus_attendances table 4 | ALTER TABLE bonus_attendances ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set the user id column for all bonus_attendances. 7 | UPDATE bonus_attendances 8 | SET user_id = id 9 | FROM users 10 | WHERE bonus_attendances.username = users.username; 11 | 12 | -- User ids should not be null 13 | ALTER TABLE bonus_attendances ALTER user_id SET NOT NULL; 14 | 15 | -- Create an index similar to the one in the creation migration 16 | CREATE INDEX ON bonus_attendances (user_id, semester_id); 17 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615401240741_alter_enrollments_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove user id column from the enrollments table 2 | ALTER TABLE enrollments DROP COLUMN user_id; 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615401240741_alter_enrollments_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user id column and foreign key constraint to the enrollments table 2 | 3 | -- Add column 4 | ALTER TABLE enrollments ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set column's values 7 | UPDATE enrollments 8 | SET user_id = id 9 | FROM users 10 | WHERE enrollments.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE enrollments ALTER user_id SET NOT NULL; 14 | 15 | -- Add unique constraint -- was not sure what to name this honestly, since it 16 | -- would normally be provided by the primary key constraint. 17 | ALTER TABLE enrollments ADD CONSTRAINT enrollments_unique_user_id_semester_id 18 | UNIQUE (semester_id, user_id); 19 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615401726640_alter_final_grade_appeals_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- remove the user_id column from the final_grade_appeal table 2 | 3 | ALTER TABLE final_grade_appeal DROP COLUMN user_id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615401726640_alter_final_grade_appeals_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user id column with foreign key constraint to final_grade_appeals table 2 | 3 | -- Add user id column and constraint 4 | ALTER TABLE final_grade_appeal ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE final_grade_appeal 8 | SET user_id = id 9 | FROM users 10 | WHERE final_grade_appeal.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE final_grade_appeal ALTER COLUMN user_id SET NOT NULL; 14 | 15 | -- Add foreign key constraint 16 | ALTER TABLE final_grade_appeal ADD FOREIGN KEY (user_id, semester_id) 17 | REFERENCES enrollments(user_id, semester_id); 18 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615403071311_alter_meeting_attendances_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop user_id column from meeting attendances. 2 | ALTER TABLE meeting_attendances DROP COLUMN user_id; -------------------------------------------------------------------------------- /rcos-data/migrations/1615403071311_alter_meeting_attendances_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user_id column and constraints to meeting attendance. 2 | 3 | -- Add column and constraint 4 | ALTER TABLE meeting_attendances ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE meeting_attendances 8 | SET user_id = id 9 | FROM users 10 | WHERE meeting_attendances.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE meeting_attendances ALTER user_id SET NOT NULL; 14 | 15 | -- Add unique constraint 16 | ALTER TABLE meeting_attendances 17 | ADD CONSTRAINT meeting_attendances_unique_meeting_id_user_id 18 | UNIQUE (meeting_id, user_id); 19 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615403588212_alter_meetings_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop host_user_id column from meetings table 2 | 3 | ALTER TABLE meetings DROP COLUMN host_user_id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615403588212_alter_meetings_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add host_user_id column and to meetings table 2 | 3 | -- Add column and constraint 4 | ALTER TABLE meetings ADD COLUMN host_user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE meetings 8 | SET host_user_id = id 9 | FROM users 10 | WHERE meetings.host_username = users.username; 11 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615404324499_alter_mentor_proposals_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove user_id column from mentor_proposals 2 | ALTER TABLE mentor_proposals DROP COLUMN user_id; 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615404324499_alter_mentor_proposals_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user_id column and foreign key constraint to mentor_proposals table 2 | 3 | -- Add column 4 | ALTER TABLE mentor_proposals ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE mentor_proposals 8 | SET user_id = id 9 | FROM users 10 | WHERE mentor_proposals.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE mentor_proposals ALTER user_id SET NOT NULL; 14 | 15 | -- Add unique constraint 16 | ALTER TABLE mentor_proposals 17 | ADD CONSTRAINT mentor_proposals_unique_semsester_id_user_id 18 | UNIQUE (semester_id, user_id); 19 | 20 | -- Add foreign key to enrollments table 21 | ALTER TABLE mentor_proposals ADD FOREIGN KEY (semester_id, user_id) 22 | REFERENCES enrollments(semester_id, user_id); 23 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615406430290_alter_pay_requests_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove user id column from pay_requests table. 2 | 3 | ALTER TABLE pay_requests DROP COLUMN user_id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615406430290_alter_pay_requests_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user_id column and constraints to pay_requests table 2 | 3 | -- Add column 4 | ALTER TABLE pay_requests ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE pay_requests 8 | SET user_id = id 9 | FROM users 10 | WHERE pay_requests.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE pay_requests ALTER COLUMN user_id SET NOT NULL; 14 | 15 | -- Add unique constraint 16 | ALTER TABLE pay_requests 17 | ADD CONSTRAINT pay_requests_unique_semester_id_user_id 18 | UNIQUE (semester_id, user_id); 19 | 20 | -- Add enrollments foreign key constraint 21 | ALTER TABLE pay_requests 22 | ADD FOREIGN KEY (semester_id, user_id) 23 | REFERENCES enrollments(semester_id, user_id); 24 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615407313244_alter_project_pitches_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop columns added to project pitches table 2 | 3 | ALTER TABLE project_pitches DROP COLUMN user_id; 4 | ALTER TABLE project_pitches DROP COLUMN reviewer_id; 5 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615407313244_alter_project_pitches_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Update project_pitches table to use new user ids 2 | 3 | -- Add columns and foreign key constraints 4 | ALTER TABLE project_pitches ADD COLUMN user_id UUID REFERENCES users(id); 5 | ALTER TABLE project_pitches ADD COLUMN reviewer_id UUID REFERENCES users(id); 6 | 7 | -- Set values 8 | UPDATE project_pitches 9 | SET user_id = id 10 | FROM users 11 | WHERE project_pitches.username = users.username; 12 | 13 | UPDATE project_pitches 14 | SET reviewer_id = id 15 | FROM users 16 | WHERE project_pitches.username = users.username; 17 | 18 | -- Add not null constraint to user_id 19 | ALTER TABLE project_pitches ALTER COLUMN user_id SET NOT NULL; 20 | 21 | -- Add unique constraint 22 | ALTER TABLE project_pitches 23 | ADD CONSTRAINT project_pitches_unique_semester_id_user_id 24 | UNIQUE (semester_id, user_id); 25 | 26 | -- Add foreign key constraints to enrollment table 27 | ALTER TABLE project_pitches 28 | ADD FOREIGN KEY (semester_id, user_id) 29 | REFERENCES enrollments(semester_id, user_id); 30 | 31 | ALTER TABLE project_pitches 32 | ADD FOREIGN KEY (semester_id, reviewer_id) 33 | REFERENCES enrollments(semester_id, user_id); 34 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615408637140_alter_project_presentation_grades_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop grader id column from project_presentation_grades 2 | 3 | ALTER TABLE project_presentation_grades DROP COLUMN grader_id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615408637140_alter_project_presentation_grades_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Add user ids to the project presentation grades table. 2 | 3 | -- Add column and constraint 4 | ALTER TABLE project_presentation_grades 5 | ADD COLUMN grader_id UUID REFERENCES users(id); 6 | 7 | -- Set values 8 | UPDATE project_presentation_grades 9 | SET grader_id = id 10 | FROM users 11 | WHERE project_presentation_grades.grader_username = users.username; 12 | 13 | -- Add not null constraint 14 | ALTER TABLE project_presentation_grades ALTER COLUMN grader_id SET NOT NULL; 15 | 16 | -- Add unique constraints to mirror primary key 17 | ALTER TABLE project_presentation_grades 18 | ADD CONSTRAINT unique_semester_id_project_id_grader_id 19 | UNIQUE (semester_id, project_id, grader_id); 20 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615409192551_alter_small_group_mentors_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop user_id column from small_group_mentors. 2 | 3 | ALTER TABLE small_group_mentors DROP COLUMN user_id; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615409192551_alter_small_group_mentors_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Alter small_group_mentors table to use user ids 2 | 3 | -- Add column and foreign key 4 | ALTER TABLE small_group_mentors ADD COLUMN user_id UUID REFERENCES users(id); 5 | 6 | -- Set values 7 | UPDATE small_group_mentors 8 | SET user_id = id 9 | FROM users 10 | WHERE small_group_mentors.username = users.username; 11 | 12 | -- Add not null constraint 13 | ALTER TABLE small_group_mentors ALTER COLUMN user_id SET NOT NULL; 14 | 15 | -- Add unique constraint 16 | ALTER TABLE small_group_mentors 17 | ADD CONSTRAINT unique_small_group_id_user_id 18 | UNIQUE (small_group_id, user_id); 19 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615409806039_alter_status_update_submissions_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop user_id and grader_id from status_update_submissions 2 | 3 | ALTER TABLE status_update_submissions DROP COLUMN user_id; 4 | ALTER TABLE status_update_submissions DROP COLUMN grader_id; 5 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615409806039_alter_status_update_submissions_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Use user ids in status update submissions 2 | 3 | -- Create columns and foreign key constraints 4 | ALTER TABLE status_update_submissions ADD COLUMN user_id UUID REFERENCES users(id); 5 | ALTER TABLE status_update_submissions ADD COLUMN grader_id UUID REFERENCES users(id); 6 | 7 | -- Set values 8 | UPDATE status_update_submissions 9 | SET user_id = id 10 | FROM users 11 | WHERE status_update_submissions.username = users.username; 12 | 13 | UPDATE status_update_submissions 14 | SET grader_id = id 15 | FROM users 16 | WHERE status_update_submissions.grader_username = users.username; 17 | 18 | -- Add not-null constraint 19 | ALTER TABLE status_update_submissions ALTER COLUMN user_id SET NOT NULL; 20 | 21 | -- Add unique constraint 22 | ALTER TABLE status_update_submissions 23 | ADD CONSTRAINT unique_status_update_id_user_id 24 | UNIQUE (status_update_id, user_id); 25 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615410882634_alter_workshop_proposals_users_foreign_key/down.sql: -------------------------------------------------------------------------------- 1 | -- Drop user_id and reviewer_id 2 | 3 | ALTER TABLE workshop_proposals DROP COLUMN user_id; 4 | ALTER TABLE workshop_proposals DROP COLUMN reviewer_id; 5 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615410882634_alter_workshop_proposals_users_foreign_key/up.sql: -------------------------------------------------------------------------------- 1 | -- Use user ids on the workshop proposals table. 2 | 3 | -- Add columns and foreign key constraints 4 | ALTER TABLE workshop_proposals ADD COLUMN user_id UUID REFERENCES users(id); 5 | ALTER TABLE workshop_proposals ADD COLUMN reviewer_id UUID REFERENCES users(id); 6 | 7 | -- Set values 8 | UPDATE workshop_proposals 9 | SET user_id = id 10 | FROM users 11 | WHERE workshop_proposals.username = users.username; 12 | 13 | UPDATE workshop_proposals 14 | SET reviewer_id = id 15 | FROM users 16 | WHERE workshop_proposals.reviewer_username = users.username; 17 | 18 | -- Add not null constraint 19 | ALTER TABLE workshop_proposals ALTER COLUMN user_id SET NOT NULL; 20 | 21 | -- Add foreign keys referencing enrollments table 22 | ALTER TABLE workshop_proposals 23 | ADD FOREIGN KEY (semester_id, user_id) 24 | REFERENCES enrollments(semester_id, user_id); 25 | 26 | ALTER TABLE workshop_proposals 27 | ADD FOREIGN KEY (semester_id, reviewer_id) 28 | REFERENCES enrollments(semester_id, user_id); 29 | 30 | -- Create index on user_ids matching the one on usernames 31 | CREATE INDEX ON workshop_proposals(user_id); 32 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615925578577_prevent_null_enrollment_fields/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove NOT NULL constraints on the same columns of the enrollments table 2 | 3 | ALTER TABLE enrollments ALTER COLUMN is_project_lead DROP NOT NULL; 4 | ALTER TABLE enrollments ALTER COLUMN is_coordinator DROP NOT NULL; 5 | ALTER TABLE enrollments ALTER COLUMN is_for_pay DROP NOT NULL; 6 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615925578577_prevent_null_enrollment_fields/up.sql: -------------------------------------------------------------------------------- 1 | -- Add constraints to the enrollments table to prevent null values 2 | 3 | ALTER TABLE enrollments ALTER COLUMN is_project_lead SET NOT NULL; 4 | ALTER TABLE enrollments ALTER COLUMN is_coordinator SET NOT NULL; 5 | ALTER TABLE enrollments ALTER COLUMN is_for_pay SET NOT NULL; 6 | -------------------------------------------------------------------------------- /rcos-data/migrations/1615926534831_constrain_user_accounts_unique/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove the created constraint 2 | ALTER TABLE user_accounts DROP CONSTRAINT unique_type_account_id; -------------------------------------------------------------------------------- /rcos-data/migrations/1615926534831_constrain_user_accounts_unique/up.sql: -------------------------------------------------------------------------------- 1 | -- Add unique constraint to user accounts so there cannot be two users linked to 2 | -- the same github/discord/etc. 3 | 4 | ALTER TABLE user_accounts 5 | ADD CONSTRAINT unique_type_account_id 6 | UNIQUE (type, account_id); -------------------------------------------------------------------------------- /rcos-data/migrations/1616616058766_prevent_null_meeting_fields/down.sql: -------------------------------------------------------------------------------- 1 | -- Lift those restrictions on the meetings table. 2 | 3 | ALTER TABLE meetings ALTER COLUMN is_public DROP NOT NULL; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1616616058766_prevent_null_meeting_fields/up.sql: -------------------------------------------------------------------------------- 1 | -- Restrict some columns of the meetings table to be not null. 2 | 3 | ALTER TABLE meetings ALTER COLUMN is_public SET NOT NULL; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1618351261768_alter_meetings_is_public/down.sql: -------------------------------------------------------------------------------- 1 | -- Reverse the effects of up.sql. 2 | 3 | -- Recreate the is_public column. 4 | ALTER TABLE meetings ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT TRUE; 5 | 6 | -- Set is_public to false on drafts. 7 | UPDATE meetings SET is_public = TRUE WHERE is_draft = FALSE; 8 | 9 | -- Remove the is_draft flag. 10 | ALTER TABLE meetings DROP COLUMN is_draft; 11 | 12 | -- Re-create the public meetings view. 13 | CREATE VIEW public_meetings AS 14 | SELECT * FROM meetings AS m 15 | WHERE m.is_public = TRUE 16 | ORDER BY m.start_date_time; 17 | 18 | COMMENT ON VIEW public_meetings IS 'View for access to public meetings'; 19 | 20 | -- Re-create original comment on is_public column. 21 | COMMENT ON COLUMN meetings.is_public 22 | IS 'True if it appears on the schedule publicly (can be used for drafts)'; 23 | -------------------------------------------------------------------------------- /rcos-data/migrations/1618351261768_alter_meetings_is_public/up.sql: -------------------------------------------------------------------------------- 1 | -- Clarify the draft flag on the meetings table. 2 | 3 | ALTER TABLE meetings ADD COLUMN is_draft BOOLEAN NOT NULL DEFAULT FALSE; 4 | 5 | -- Indicate that this flag denotes draft status. 6 | COMMENT ON COLUMN meetings.is_draft 7 | IS 'Flag to indicate this meeting is a draft, and the details are not final.'; 8 | 9 | -- Set the value to true if the old flag was false. 10 | UPDATE meetings SET is_draft = TRUE WHERE is_public = FALSE; 11 | 12 | -- Drop the public_meetings view. We could update it, but it's preferable for 13 | -- clients to just use GraphQl filters and ordering in their queries. 14 | DROP VIEW public_meetings; 15 | 16 | -- Remove the old flag. 17 | ALTER TABLE meetings DROP COLUMN is_public; 18 | -------------------------------------------------------------------------------- /rcos-data/migrations/1622138042193_add_sysadmin_role/down.sql: -------------------------------------------------------------------------------- 1 | -- Remove user role for sysadmins 2 | 3 | -- Recreate the old enum 4 | CREATE TYPE old_user_role AS ENUM ('student', 'faculty', 'faculty_advisor', 'alumn', 'external', 'external_mentor'); 5 | COMMENT ON TYPE old_user_role IS 'The user''s position within RCOS'; 6 | 7 | -- Convert all sysadmin to faculty advisors. They should have similar permissions. 8 | UPDATE users SET role = 'faculty_advisor' WHERE role = 'sysadmin'; 9 | 10 | -- Drop the faculty_advisors view since it prevents us from changing role type. 11 | DROP VIEW faculty_advisors; 12 | 13 | -- Change the user role type 14 | ALTER TABLE users ALTER COLUMN role TYPE old_user_role USING (role::text::old_user_role); 15 | 16 | -- Drop the original enum 17 | DROP TYPE user_role; 18 | 19 | -- Rename the new type 20 | ALTER TYPE old_user_role RENAME TO user_role; 21 | 22 | -- Re-create the faculty advisors view 23 | CREATE VIEW faculty_advisors AS 24 | SELECT u.username, u.preferred_name, u.first_name, u.last_name 25 | FROM users AS u 26 | WHERE u.role = 'faculty_advisor'; 27 | 28 | COMMENT ON VIEW faculty_advisors IS 'View for access to Faculty Advisors'; 29 | -------------------------------------------------------------------------------- /rcos-data/migrations/1622138042193_add_sysadmin_role/up.sql: -------------------------------------------------------------------------------- 1 | -- Create a user role for sysadmins 2 | 3 | ALTER TYPE user_role ADD VALUE 'sysadmin'; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1623899398293_alter_meeetings_agenda/down.sql: -------------------------------------------------------------------------------- 1 | -- Undo up.sql. 2 | 3 | -- Add back the agenda column. 4 | ALTER TABLE meetings ADD COLUMN agenda VARCHAR[] DEFAULT '{}'::VARCHAR[]; 5 | COMMENT ON COLUMN meetings.agenda IS 'List of agenda items that will be covered in the meeting'; 6 | 7 | -- Drop description column. 8 | ALTER TABLE meetings DROP COLUMN description; 9 | -------------------------------------------------------------------------------- /rcos-data/migrations/1623899398293_alter_meeetings_agenda/up.sql: -------------------------------------------------------------------------------- 1 | -- This migration replaces the agenda column of the meetings table 2 | -- with a more general purpose description column. 3 | 4 | -- Add description column. 5 | ALTER TABLE meetings ADD COLUMN description TEXT NOT NULL DEFAULT ''; 6 | COMMENT ON COLUMN meetings.description IS 'Description of the meeting in CommonMark markdown or plaintext.'; 7 | 8 | -- Convert all agendas to descriptions. This is the word agenda, followed by 9 | -- a bulleted list of the agenda items on newlines. 10 | UPDATE meetings 11 | SET description = E'Agenda:\n' || array_to_string(agenda, E'\n- ') 12 | WHERE array_length(agenda, 1) > 1; 13 | 14 | -- Drop the agenda column. 15 | ALTER TABLE meetings DROP COLUMN agenda; 16 | -------------------------------------------------------------------------------- /rcos-data/migrations/1649450066130_alter_table_public_projects_add_column_updated_at/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- alter table "public"."projects" add column "updated_at" timestamptz 4 | -- null; 5 | 6 | ALTER table "public"."projects" DROP COLUMN IF EXISTS updated_at; 7 | -------------------------------------------------------------------------------- /rcos-data/migrations/1649450066130_alter_table_public_projects_add_column_updated_at/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."projects" add column "updated_at" timestamptz 2 | default now(); 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1649450545541_create_projects_update_trigger/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- CREATE OR REPLACE FUNCTION update_modified_now() RETURNS trigger AS $update_modified_now$ 4 | -- BEGIN 5 | -- IF NEW.update_at IS NULL THEN 6 | -- NEW.update_at := current_timestamp; 7 | -- END IF; 8 | -- return NEW; 9 | -- END; 10 | -- $update_modified_now$ LANGUAGE plpgsql; 11 | -- 12 | -- CREATE TRIGGER project_update_modified BEFORE INSERT OR UPDATE on projects 13 | -- FOR EACH ROW EXECUTE FUNCTION update_modified_now(); 14 | 15 | DROP TRIGGER IF EXISTS project_update_modified on projects; 16 | DROP FUNCTION IF EXISTS update_modified_now(); -------------------------------------------------------------------------------- /rcos-data/migrations/1649450545541_create_projects_update_trigger/up.sql: -------------------------------------------------------------------------------- 1 | CREATE OR REPLACE FUNCTION update_modified_now() RETURNS trigger AS $update_modified_now$ 2 | BEGIN 3 | IF NEW.updated_at = OLD.updated_at THEN 4 | NEW.updated_at := current_timestamp; 5 | END IF; 6 | return NEW; 7 | END; 8 | $update_modified_now$ LANGUAGE plpgsql; 9 | 10 | CREATE TRIGGER project_update_modified BEFORE UPDATE on projects 11 | FOR EACH ROW EXECUTE FUNCTION update_modified_now(); 12 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990035352_create_table_public_project_stack/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."project_stack"; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990035352_create_table_public_project_stack/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."project_stack" ("repo" url NOT NULL, "id" serial NOT NULL, PRIMARY KEY ("id") );COMMENT ON TABLE "public"."project_stack" IS E'Stores the stack associated with a project'; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990131409_alter_table_public_project_stack_alter_column_repo/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" rename column "language" to "repo"; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990131409_alter_table_public_project_stack_alter_column_repo/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" rename column "repo" to "language"; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990322555_alter_table_public_project_stack_add_column_project_id/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- alter table "public"."project_stack" add column "project_id" uuid 4 | -- not null; 5 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990322555_alter_table_public_project_stack_add_column_project_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" add column "project_id" uuid 2 | not null; 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990415144_alter_table_public_project_stack_drop_column_project_id/down.sql: -------------------------------------------------------------------------------- 1 | comment on column "public"."project_stack"."project_id" is E'Stores the stack associated with a project'; 2 | alter table "public"."project_stack" alter column "project_id" drop not null; 3 | alter table "public"."project_stack" add column "project_id" uuid; 4 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990415144_alter_table_public_project_stack_drop_column_project_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" drop column "project_id" cascade; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990471268_alter_table_public_project_stack_add_column_project_id/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- alter table "public"."project_stack" add column "project_id" integer 4 | -- not null; 5 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990471268_alter_table_public_project_stack_add_column_project_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" add column "project_id" integer 2 | not null; 3 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990490497_set_fk_public_project_stack_project_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" drop constraint "project_stack_project_id_fkey"; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1666990490497_set_fk_public_project_stack_project_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."project_stack" 2 | add constraint "project_stack_project_id_fkey" 3 | foreign key ("project_id") 4 | references "public"."projects" 5 | ("project_id") on update restrict on delete cascade; 6 | -------------------------------------------------------------------------------- /rcos-data/migrations/1667334258367_create_table_public_project_repositories/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."project_repositories"; 2 | -------------------------------------------------------------------------------- /rcos-data/migrations/1667334258367_create_table_public_project_repositories/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."project_repositories" ("language" url NOT NULL, "id" serial NOT NULL, "project_id" integer NOT NULL, PRIMARY KEY ("id") , FOREIGN KEY ("project_id") REFERENCES "public"."projects"("project_id") ON UPDATE restrict ON DELETE cascade);COMMENT ON TABLE "public"."project_repositories" IS E'All of the repo\'s associated with a project'; 2 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # unstable_feautures = true 2 | # merge_imports = true 3 | # reorder_impl_items = true 4 | 5 | reorder_imports = true 6 | reorder_modules = true 7 | merge_derives = true 8 | newline_style = "Native" 9 | -------------------------------------------------------------------------------- /src/api/discord/mod.rs: -------------------------------------------------------------------------------- 1 | //! Discord API interactions authenticated with the Telescope bot token. 2 | 3 | use crate::env::global_config; 4 | use crate::error::TelescopeError; 5 | use serenity::http::Http; 6 | use serenity::model::id::RoleId; 7 | 8 | lazy_static! { 9 | static ref DISCORD_API_CLIENT: Http = 10 | Http::new_with_token(global_config().as_ref().discord_config.bot_token.as_str()); 11 | } 12 | 13 | /// Get a reference to the global lazily evaluated static discord api client object. 14 | pub fn global_discord_client() -> &'static Http { 15 | DISCORD_API_CLIENT.as_ref() 16 | } 17 | 18 | /// Get the ID of the verified role on the RCOS discord if it exists. 19 | pub async fn rcos_discord_verified_role_id() -> Result, TelescopeError> { 20 | // Get the RCOS Guild ID. 21 | let rcos_discord: u64 = global_config().discord_config.rcos_guild_id(); 22 | 23 | // Get role 24 | Ok(global_discord_client() 25 | .get_guild_roles(rcos_discord) 26 | .await 27 | .map_err(|err| { 28 | error!("Could not get RCOS Discord Roles. Internal error: {}", err); 29 | TelescopeError::serenity_error(err) 30 | })? 31 | .iter() 32 | // We use a simple string comparison for now. We can change this to use 33 | // something else later on if needed. 34 | .find(|role| role.name.eq_ignore_ascii_case("Verified")) 35 | // Extract the ID from the Discord Role. 36 | .map(|role| role.id)) 37 | } 38 | -------------------------------------------------------------------------------- /src/api/github/users/authenticated_user.rs: -------------------------------------------------------------------------------- 1 | //! Query to get authenticated GitHub user. 2 | 3 | // Import serializable URL type for query types. 4 | use url::Url as URI; 5 | 6 | #[derive(GraphQLQuery)] 7 | #[graphql( 8 | schema_path = "graphql/github/schema.json", 9 | query_path = "graphql/github/users/authenticated_user.graphql", 10 | response_derives = "Debug,Clone,Serialize" 11 | )] 12 | pub struct AuthenticatedUser; 13 | -------------------------------------------------------------------------------- /src/api/github/users/mod.rs: -------------------------------------------------------------------------------- 1 | //! GitHub user related authentication and queries. 2 | 3 | pub mod authenticated_user; 4 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/mod.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL types queries and mutations related to entities on the RCOS discord server. 2 | /// Type representing the different kinds of channels that can be associated with a small 3 | /// group or a project. 4 | pub mod project; 5 | pub mod small_group; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq)] 8 | #[serde(rename_all = "snake_case")] 9 | pub enum ChannelType { 10 | /// A Discord voice channel. 11 | DiscordVoice, 12 | /// A Discord text channel. 13 | DiscordText, 14 | } 15 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/project/create_project_channel.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a discord channel for a given projct id. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | 6 | use crate::error::TelescopeError; 7 | 8 | /// Type representing GraphQL mutation to create channel for a project. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/discord_associations/project/create_project_channel.graphql" 13 | )] 14 | pub struct CreateOneProjectChannel; 15 | 16 | impl CreateOneProjectChannel { 17 | pub async fn execute( 18 | project_id: i64, 19 | channel_id: String, 20 | kind: channel_type, 21 | ) -> Result, TelescopeError> { 22 | send_query::(create_one_project_channel::Variables { 23 | project_id, 24 | channel_id, 25 | kind, 26 | }) 27 | .await 28 | .map(|response| { 29 | response 30 | .insert_project_channels_one 31 | .map(|obj| obj.channel_id) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/project/create_project_role.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a discord channel for a given projct id. 2 | 3 | use crate::api::rcos::send_query; 4 | 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL mutation to create channel for a project. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/discord_associations/project/create_project_role.graphql" 12 | )] 13 | pub struct CreateOneProjectRole; 14 | 15 | impl CreateOneProjectRole { 16 | pub async fn execute( 17 | project_id: i64, 18 | role_id: String, 19 | ) -> Result, TelescopeError> { 20 | send_query::(create_one_project_role::Variables { 21 | project_id, 22 | role_id, 23 | }) 24 | .await 25 | .map(|response| response.insert_project_roles_one.map(|obj| obj.role_id)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/project/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_project_channel; 2 | pub mod create_project_role; 3 | pub mod project_info; 4 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/project/project_info.rs: -------------------------------------------------------------------------------- 1 | use crate::api::rcos::{prelude::*, search_strings::resolve_search_string, send_query}; 2 | use crate::error::TelescopeError; 3 | use chrono::Utc; 4 | 5 | /// Projects per page. 6 | const PER_PAGE: u32 = 20; 7 | /// GraphQL query to get projects with enrollments in an ongoing semester. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/discord_associations/project/projects.graphql", 12 | response_derives = "Debug,Clone,Serialize" 13 | )] 14 | pub struct CurrProjects; 15 | 16 | impl CurrProjects { 17 | pub async fn get( 18 | page: u32, 19 | search: Option, 20 | ) -> Result { 21 | send_query::(curr_projects::Variables { 22 | offset: (PER_PAGE * page) as i64, 23 | search: resolve_search_string(search), 24 | now: Utc::today().naive_utc(), 25 | }) 26 | .await 27 | } 28 | } 29 | 30 | #[derive(GraphQLQuery)] 31 | #[graphql( 32 | schema_path = "graphql/rcos/schema.json", 33 | query_path = "graphql/rcos/discord_associations/project/find_project.graphql", 34 | response_derives = "Debug,Clone,Serialize" 35 | )] 36 | pub struct FindProject; 37 | 38 | impl FindProject { 39 | pub async fn get_by_id(id: i64) -> Result { 40 | send_query::(find_project::Variables { id: id }).await 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/small_group/create_small_group_category.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a discord channel for a given projct id. 2 | 3 | use crate::api::rcos::send_query; 4 | 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL mutation to create channel for a project. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/discord_associations/small_group/create_small_group_category.graphql" 12 | )] 13 | pub struct CreateOneSmallGroupCategory; 14 | 15 | impl CreateOneSmallGroupCategory { 16 | pub async fn execute( 17 | small_group_id: i64, 18 | category_id: String, 19 | ) -> Result, TelescopeError> { 20 | send_query::(create_one_small_group_category::Variables { 21 | small_group_id, 22 | category_id, 23 | }) 24 | .await 25 | .map(|response| { 26 | response 27 | .insert_small_group_categories_one 28 | .map(|obj| obj.category_id) 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/small_group/create_small_group_channel.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a discord channel for a given project id. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | 6 | use crate::error::TelescopeError; 7 | 8 | /// Type representing GraphQL mutation to create channel for a project. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/discord_associations/small_group/create_small_group_channel.graphql" 13 | )] 14 | pub struct CreateOneSmallGroupChannel; 15 | 16 | impl CreateOneSmallGroupChannel { 17 | pub async fn execute( 18 | small_group_id: i64, 19 | channel_id: String, 20 | kind: channel_type, 21 | ) -> Result, TelescopeError> { 22 | send_query::(create_one_small_group_channel::Variables { 23 | small_group_id, 24 | channel_id, 25 | kind, 26 | }) 27 | .await 28 | .map(|response| { 29 | response 30 | .insert_small_group_channels_one 31 | .map(|obj| obj.channel_id) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/small_group/create_small_group_role.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a discord channel for a given projct id. 2 | 3 | use crate::api::rcos::send_query; 4 | 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL mutation to create channel for a project. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/discord_associations/small_group/create_small_group_role.graphql" 12 | )] 13 | pub struct CreateOneSmallGroupRole; 14 | 15 | impl CreateOneSmallGroupRole { 16 | pub async fn execute( 17 | small_group_id: i64, 18 | role_id: String, 19 | ) -> Result, TelescopeError> { 20 | send_query::(create_one_small_group_role::Variables { 21 | small_group_id, 22 | role_id, 23 | }) 24 | .await 25 | .map(|response| response.insert_small_group_roles_one.map(|obj| obj.role_id)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/small_group/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_small_group_category; 2 | pub mod create_small_group_channel; 3 | pub mod create_small_group_role; 4 | pub mod small_group_info; 5 | -------------------------------------------------------------------------------- /src/api/rcos/discord_associations/small_group/small_group_info.rs: -------------------------------------------------------------------------------- 1 | use crate::api::rcos::discord_associations::small_group::small_group_info::curr_small_groups::ResponseData; 2 | use crate::api::rcos::discord_associations::small_group::small_group_info::curr_small_groups::Variables; 3 | use crate::api::rcos::{prelude::*, search_strings::resolve_search_string, send_query}; 4 | use crate::error::TelescopeError; 5 | use chrono::Utc; 6 | 7 | /// Projects per page. 8 | const PER_PAGE: u32 = 20; 9 | 10 | /// GraphQL query to get projects with enrollments in an ongoing semester. 11 | #[derive(GraphQLQuery)] 12 | #[graphql( 13 | schema_path = "graphql/rcos/schema.json", 14 | query_path = "graphql/rcos/discord_associations/small_group/small_groups.graphql", 15 | response_derives = "Debug,Clone,Serialize" 16 | )] 17 | pub struct CurrSmallGroups; 18 | impl CurrSmallGroups { 19 | pub async fn get(page: u32, search: Option) -> Result { 20 | send_query::(Variables { 21 | offset: (PER_PAGE * page) as i64, 22 | search: resolve_search_string(search), 23 | now: Utc::today().naive_utc(), 24 | }) 25 | .await 26 | } 27 | } 28 | #[derive(GraphQLQuery)] 29 | #[graphql( 30 | schema_path = "graphql/rcos/schema.json", 31 | query_path = "graphql/rcos/discord_associations/small_group/find_small_group.graphql", 32 | response_derives = "Debug,Clone,Serialize" 33 | )] 34 | pub struct FindSmallGroup; 35 | impl FindSmallGroup { 36 | pub async fn get_by_id(id: i64) -> Result { 37 | send_query::(find_small_group::Variables { id: id }).await 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/api/rcos/landing_page_stats.rs: -------------------------------------------------------------------------------- 1 | //! Module for Landing Page statistics query and data extraction. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | use chrono::Utc; 6 | 7 | /// GraphQL Query for landing page statistics. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/stats/landing_page.graphql", 12 | response_derives = "Serialize" 13 | )] 14 | pub struct LandingPageStatistics; 15 | 16 | use self::landing_page_statistics::{ResponseData, Variables}; 17 | 18 | impl LandingPageStatistics { 19 | /// Get the landing page statistics from the RCOS API. 20 | pub async fn get() -> Result { 21 | return send_query::(Variables { 22 | now: Utc::today().naive_utc(), 23 | }) 24 | .await; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/creation/context.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get context for meeting creation. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | use chrono::Utc; 7 | 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/meetings/creation/context.graphql", 12 | response_derives = "Debug,Clone,Serialize", 13 | variables_derives = "Deserialize" 14 | )] 15 | pub struct CreationContext; 16 | 17 | impl CreationContext { 18 | /// Get the meeting creation context. 19 | /// 20 | /// For meeting edits, semesters may be manually included by ID. otherwise, only ongoing and 21 | /// future semesters will be included. 22 | pub async fn execute( 23 | host: Option, 24 | include_semesters: Vec, 25 | ) -> Result { 26 | send_query::(creation_context::Variables { 27 | host: host.map(|h| vec![h]).unwrap_or(vec![]), 28 | today: Utc::today().naive_utc(), 29 | include_semesters, 30 | }) 31 | .await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/creation/host_selection.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get host selection options ans suggestions. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::search_strings::resolve_search_string; 5 | use crate::api::rcos::send_query; 6 | use crate::error::TelescopeError; 7 | use chrono::Utc; 8 | 9 | /// Type representing host selection GraphQL query. 10 | #[derive(GraphQLQuery)] 11 | #[graphql( 12 | schema_path = "graphql/rcos/schema.json", 13 | query_path = "graphql/rcos/meetings/creation/host_selection.graphql", 14 | response_derives = "Debug,Clone,Serialize" 15 | )] 16 | pub struct HostSelection; 17 | 18 | impl HostSelection { 19 | /// Get the host selection data from the RCOS API. 20 | pub async fn get( 21 | search: Option, 22 | ) -> Result { 23 | send_query::(host_selection::Variables { 24 | search: resolve_search_string(search), 25 | now: Utc::today().naive_utc(), 26 | }) 27 | .await 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/creation/mod.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL queries and mutations related to meeting creation. 2 | 3 | pub mod context; 4 | pub mod create; 5 | pub mod host_selection; 6 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/delete.rs: -------------------------------------------------------------------------------- 1 | //! Meeting deletion mutation. 2 | 3 | use crate::api::rcos::send_query; 4 | use crate::error::TelescopeError; 5 | 6 | /// Type representing GraphQL mutation to delete a meeting and associated attendances. 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/meetings/delete.graphql", 11 | response_derives = "Debug,Clone,Serialize", 12 | variables_derives = "Debug,Clone,Copy" 13 | )] 14 | pub struct DeleteMeeting; 15 | 16 | impl DeleteMeeting { 17 | /// Delete a meeting and all associated attendances. 18 | pub async fn execute(meeting_id: i64) -> Result { 19 | send_query::(delete_meeting::Variables { meeting_id }).await 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/edit.rs: -------------------------------------------------------------------------------- 1 | //! Meeting edit mutation and host selection query. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL meeting edit mutation. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/meetings/edit/edit.graphql", 12 | response_derives = "Debug,Copy,Clone,Serialize" 13 | )] 14 | pub struct EditMeeting; 15 | 16 | impl EditMeeting { 17 | /// Execute a meeting edit mutation. Return the ID of the edited meeting if any 18 | /// changes were made. 19 | pub async fn execute(vars: edit_meeting::Variables) -> Result, TelescopeError> { 20 | send_query::(vars) 21 | .await 22 | .map(|response| response.update_meetings_by_pk.map(|obj| obj.meeting_id)) 23 | } 24 | } 25 | 26 | /// Type representing host selection query used while editing meetings. 27 | #[derive(GraphQLQuery)] 28 | #[graphql( 29 | schema_path = "graphql/rcos/schema.json", 30 | query_path = "graphql/rcos/meetings/edit/host_selection.graphql", 31 | response_derives = "Debug,Clone,Serialize" 32 | )] 33 | pub struct EditHostSelection; 34 | 35 | impl EditHostSelection { 36 | /// Get the available hosts for this meeting. 37 | pub async fn get(meeting_id: i64) -> Result { 38 | send_query::(edit_host_selection::Variables { meeting_id }).await 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/get.rs: -------------------------------------------------------------------------------- 1 | //! List meetings query. 2 | 3 | use crate::api::rcos::meetings::MeetingType; 4 | use crate::api::rcos::prelude::*; 5 | use crate::api::rcos::send_query; 6 | use crate::error::TelescopeError; 7 | use chrono::{DateTime, Utc}; 8 | 9 | /// Type representing public RCOS meetings. 10 | #[derive(GraphQLQuery)] 11 | #[graphql( 12 | schema_path = "graphql/rcos/schema.json", 13 | query_path = "graphql/rcos/meetings/get.graphql", 14 | response_derives = "Debug,Clone,Serialize" 15 | )] 16 | pub struct Meetings; 17 | 18 | use self::meetings::{MeetingsMeetings, Variables}; 19 | 20 | impl Meetings { 21 | /// Get the meetings between two times, optionally filter to finalized meetings only. 22 | pub async fn get( 23 | start: DateTime, 24 | end: DateTime, 25 | include_drafts: bool, 26 | accept_types: Vec, 27 | ) -> Result, TelescopeError> { 28 | Ok(send_query::(Variables { 29 | start, 30 | end, 31 | include_drafts, 32 | accept_types, 33 | }) 34 | .await? 35 | .meetings) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/get_by_id.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get a meeting by its ID. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing public RCOS meetings. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/meetings/get_by_id.graphql", 12 | response_derives = "Debug,Clone,Serialize" 13 | )] 14 | pub struct Meeting; 15 | 16 | use self::meeting::{MeetingMeeting, Variables}; 17 | 18 | impl Meeting { 19 | /// Get a meeting by its ID. 20 | pub async fn get(meeting_id: i64) -> Result, TelescopeError> { 21 | Ok(send_query::(Variables { id: meeting_id }) 22 | // Wait for API response 23 | .await? 24 | // Extract the meeting object. 25 | .meeting) 26 | } 27 | } 28 | 29 | impl MeetingMeeting { 30 | /// Get the title of this meeting. This is the user-defined title if there is one, otherwise 31 | /// a title is constructed from the start date and meeting type. 32 | pub fn title(&self) -> String { 33 | // Check for a user-defined title. 34 | if self.title.is_some() { 35 | return self.title.clone().unwrap(); 36 | } 37 | 38 | // Otherwise create a title. 39 | format!( 40 | "RCOS {} - {}", 41 | self.type_, 42 | self.start_date_time.format("%B %_d, %Y") 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/get_host.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get the user ID of the host of a meeting by the meeting's ID. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | 6 | #[derive(GraphQLQuery)] 7 | #[graphql( 8 | schema_path = "graphql/rcos/schema.json", 9 | query_path = "graphql/rcos/meetings/get_host.graphql", 10 | response_derives = "Debug,Clone,Serialize" 11 | )] 12 | pub struct MeetingHost; 13 | 14 | impl MeetingHost { 15 | /// Get the user ID of the host of a meeting if there is one. 16 | pub async fn get(meeting_id: i64) -> Result, TelescopeError> { 17 | send_query::(meeting_host::Variables { meeting_id }) 18 | .await 19 | .map(|response| { 20 | response 21 | .meetings_by_pk 22 | .and_then(|meeting| meeting.host) 23 | .map(|host| host.id) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/api/rcos/meetings/mod.rs: -------------------------------------------------------------------------------- 1 | //! Queries and mutations to the RCOS API for meeting data. 2 | 3 | pub mod authorization_for; 4 | pub mod creation; 5 | pub mod delete; 6 | pub mod edit; 7 | pub mod get; 8 | pub mod get_by_id; 9 | pub mod get_host; 10 | 11 | /// List of all existing meeting type variants. 12 | pub const ALL_MEETING_TYPES: [MeetingType; 8] = [ 13 | MeetingType::LargeGroup, 14 | MeetingType::SmallGroup, 15 | MeetingType::Presentations, 16 | MeetingType::BonusSession, 17 | MeetingType::Grading, 18 | MeetingType::Mentors, 19 | MeetingType::Coordinators, 20 | MeetingType::Other, 21 | ]; 22 | 23 | /// The type of a meeting. 24 | #[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Display)] 25 | #[serde(rename_all = "snake_case")] 26 | pub enum MeetingType { 27 | #[display(fmt = "Large Group")] 28 | LargeGroup, 29 | #[display(fmt = "Small Group")] 30 | SmallGroup, 31 | #[display(fmt = "Presentation")] 32 | Presentations, 33 | #[display(fmt = "Bonus Session")] 34 | BonusSession, 35 | #[display(fmt = "Grading Meeting")] 36 | Grading, 37 | #[display(fmt = "Mentor Meeting")] 38 | Mentors, 39 | #[display(fmt = "Coordinator Meeting")] 40 | Coordinators, 41 | #[display(fmt = "Uncategorized Meeting")] 42 | Other, 43 | } 44 | -------------------------------------------------------------------------------- /src/api/rcos/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Namespace types used by the RCOS API. 2 | 3 | // Ignore compiler warnings for lowercase typenames. 4 | #![allow(nonstandard_style)] 5 | 6 | use crate::api::rcos::discord_associations::ChannelType; 7 | use crate::api::rcos::{ 8 | meetings::MeetingType, 9 | users::{UserAccountType, UserRole}, 10 | }; 11 | use chrono::{DateTime, NaiveDate, Utc}; 12 | use url::Url; 13 | 14 | /// Timestamp with Timezone. 15 | pub type timestamptz = DateTime; 16 | 17 | /// Date (the ones in the database do not have a timezone, 18 | /// but should be interpreted as eastern time). 19 | pub type date = NaiveDate; 20 | 21 | /// User's role. 22 | pub type user_role = UserRole; 23 | 24 | /// User account variants. 25 | pub type user_account = UserAccountType; 26 | 27 | /// Meeting variants. 28 | pub type meeting_type = MeetingType; 29 | 30 | /// List of strings for some reason not properly set in Hasura. 31 | pub type _varchar = Vec; 32 | 33 | /// List of urls for some reason not properly set in Hasura. 34 | pub type _url = Vec; 35 | 36 | /// Discord channel association variants. 37 | pub type channel_type = ChannelType; 38 | 39 | /// UUID type alias for hasura. 40 | pub type uuid = ::uuid::Uuid; 41 | -------------------------------------------------------------------------------- /src/api/rcos/projects/create.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL mutation to create a project. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | use chrono::{DateTime, Utc}; 7 | use url::Url; 8 | 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/projects/create.graphql", 13 | response_derives = "Debug,Copy,Clone,Serialize" 14 | )] 15 | pub struct CreateProject; 16 | 17 | /// Trim the whitespace off a string. If the trimmed string is empty default to None. 18 | pub fn normalize_url(url: Option) -> Option { 19 | url.and_then(|string| (!string.trim().is_empty()).then(|| string)) 20 | } 21 | impl CreateProject { 22 | /// Execute a Project creation mutation. Return the created Project's ID. 23 | pub async fn execute( 24 | title: Option, 25 | stack: Option>, 26 | repository_urls: Option>, 27 | homepage_url: Option, 28 | description: Option, 29 | cover_image_url: Option, 30 | 31 | ) -> Result, TelescopeError> { 32 | send_query::(create_project::Variables { 33 | title, 34 | stack, 35 | repository_urls, 36 | homepage_url, 37 | description, 38 | cover_image_url, 39 | }) 40 | .await 41 | .map(|response| response.insert_projects_one.map(|obj| obj.project_id)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/api/rcos/projects/get_by_id.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get a project by its ID. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing public RCOS projects. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/projects/get_by_id.graphql", 12 | response_derives = "Debug,Clone,Serialize" 13 | )] 14 | pub struct Project; 15 | 16 | use self::project::{ProjectProject, Variables}; 17 | 18 | impl Project { 19 | /// Get a Project by its ID. 20 | pub async fn get(project_id: i64) -> Result, TelescopeError> { 21 | Ok(send_query::(Variables { id: project_id }) 22 | // Wait for API response 23 | .await? 24 | // Extract the project object. 25 | .project) 26 | } 27 | } 28 | 29 | impl ProjectProject{ 30 | /// Get the title of this project. This is the user-defined title if there is one, otherwise 31 | /// a title is constructed from the start date and project type. 32 | pub fn title(&self) -> String { 33 | self.title.clone() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/rcos/projects/mod.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API interactions related to projects. 2 | 3 | pub mod projects_page; 4 | pub mod get_by_id; 5 | pub mod authorization_for; 6 | // pub mod create; 7 | -------------------------------------------------------------------------------- /src/api/rcos/search_strings.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions for handling search strings before being sent to Hasura. 2 | 3 | use regex::Regex; 4 | use std::borrow::Cow; 5 | 6 | lazy_static! { 7 | static ref SEARCH_REGEX: Regex = Regex::new(r"[@%\\]").unwrap(); 8 | } 9 | 10 | /// Escape a search string by putting a back-slash before all 11 | /// special characters (`_`, `%`, or `\`). 12 | fn escape_search_string(search: &str) -> Cow<'_, str> { 13 | // Replace all instances of the matched regex with themself preceded 14 | // by a back-slash 15 | SEARCH_REGEX.replace_all(search, "\\$0") 16 | } 17 | 18 | /// Escape a search string and format with hasura regular expression characters 19 | /// or produce the default (all-accepting) search string. 20 | pub fn resolve_search_string(search: Option) -> String { 21 | search 22 | // Escape the search string and surround it in `%`s to match on any sequence. 23 | .map(|s| format!("%{}%", escape_search_string(s.as_str()))) 24 | // Default to match any user on no search string. 25 | .unwrap_or("%".into()) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/current/info.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query for info about the current semester. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | use chrono::prelude::*; 6 | 7 | /// Type representing GraphQL query for current semester data. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/semesters/current/info.graphql", 12 | response_derives = "Debug,Clone,Serialize" 13 | )] 14 | pub struct CurrentSemesters; 15 | 16 | impl CurrentSemesters{ 17 | pub async fn get() -> Result { 18 | send_query::(current_semesters::Variables{ 19 | now: Utc::now().naive_utc().date(), 20 | }) 21 | .await 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/current/mod.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL operations on the current semesters. 2 | 3 | pub mod info; 4 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/get.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get semester records. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | 6 | /// Type representing GraphQL query for current semester data. 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/semesters/get.graphql", 11 | response_derives = "Debug,Clone,Serialize" 12 | )] 13 | pub struct Semesters; 14 | 15 | /// Semester records to get per page. 16 | pub const PER_PAGE: u32 = 20; 17 | 18 | impl Semesters { 19 | /// Get semester records (using a zero indexed page number) 20 | pub async fn get(page_num: u32) -> Result { 21 | send_query::(semesters::Variables { 22 | limit: PER_PAGE as i64, 23 | offset: (page_num * PER_PAGE) as i64, 24 | }) 25 | .await 26 | } 27 | } 28 | 29 | impl semesters::ResponseData { 30 | /// Get the semester count if available. 31 | pub fn semester_count(&self) -> Option { 32 | Some(self.semesters_aggregate.aggregate.as_ref()?.count) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/get_by_id.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get a single semester record by ID. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL mutation to make changes to a semester. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/semesters/get_by_id.graphql", 12 | response_derives = "Debug,Clone,Serialize" 13 | )] 14 | pub struct Semester; 15 | 16 | impl Semester { 17 | /// Get a semester record by ID. 18 | pub async fn get_by_id( 19 | id: String, 20 | ) -> Result, TelescopeError> { 21 | send_query::(semester::Variables { id }) 22 | .await 23 | .map(|data| data.semesters_by_pk) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/mod.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL operations on semester data. 2 | 3 | pub mod current; 4 | pub mod get; 5 | pub mod get_by_id; 6 | pub mod mutations; 7 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/mutations/create.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL mutation to create a semester in the RCOS dataabse. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | use chrono::NaiveDate; 7 | 8 | /// Type representing GraphQL mutation to create semester. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/semesters/mutations/create.graphql", 13 | response_derives = "Debug,Clone,Serialize" 14 | )] 15 | pub struct CreateSemester; 16 | 17 | impl CreateSemester { 18 | /// Create a semester. Return the semester ID or an error. 19 | pub async fn execute( 20 | id: String, 21 | title: String, 22 | start: NaiveDate, 23 | end: NaiveDate, 24 | ) -> Result { 25 | return send_query::(create_semester::Variables { 26 | id, 27 | title, 28 | start, 29 | end, 30 | }) 31 | .await 32 | // Extract semester ID. 33 | .map(|r| r.insert_semesters_one.unwrap().semester_id); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/mutations/edit.rs: -------------------------------------------------------------------------------- 1 | //! Edit mutation on semesters. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | use chrono::NaiveDate; 7 | 8 | /// Type representing GraphQL mutation to make changes to a semester. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/semesters/mutations/edit.graphql", 13 | response_derives = "Debug,Clone,Serialize" 14 | )] 15 | pub struct EditSemester; 16 | 17 | impl EditSemester { 18 | /// Send a semester edit mutation. Return a semester ID if there was a semester found and edited. 19 | pub async fn execute( 20 | id: String, 21 | new_title: String, 22 | new_start: NaiveDate, 23 | new_end: NaiveDate, 24 | ) -> Result, TelescopeError> { 25 | send_query::(edit_semester::Variables { 26 | semester_id: id, 27 | set_title: Some(new_title), 28 | set_start: Some(new_start), 29 | set_end: Some(new_end), 30 | }) 31 | .await 32 | .map(|data| data.update_semesters_by_pk.map(|obj| obj.semester_id)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/rcos/semesters/mutations/mod.rs: -------------------------------------------------------------------------------- 1 | //! Mutations on semesters in the RCOS database. 2 | 3 | pub mod create; 4 | pub mod edit; 5 | -------------------------------------------------------------------------------- /src/api/rcos/users/accounts/for_user.rs: -------------------------------------------------------------------------------- 1 | //! Lookup all the user accounts for a given user. 2 | 3 | // Namespacing 4 | use crate::api::rcos::prelude::*; 5 | use crate::api::rcos::users::UserAccountType as user_account; 6 | 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/users/accounts/for_user.graphql" 11 | )] 12 | pub struct UserAccounts; 13 | 14 | use crate::api::rcos::send_query; 15 | use crate::error::TelescopeError; 16 | use user_accounts::Variables; 17 | 18 | impl UserAccounts { 19 | /// Create the parameters for an accounts lookup query. 20 | fn make_variables(user_id: uuid) -> Variables { 21 | Variables { user_id } 22 | } 23 | 24 | /// Send a lookup query for a user's linked accounts. 25 | pub async fn send(user_id: uuid) -> Result, TelescopeError> { 26 | send_query::(Self::make_variables(user_id)) 27 | .await 28 | .map(|response| { 29 | response 30 | .user_accounts 31 | .into_iter() 32 | .map(|linked_account| (linked_account.type_, linked_account.account_id)) 33 | .collect() 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/api/rcos/users/accounts/lookup.rs: -------------------------------------------------------------------------------- 1 | //! Lookup an account by the type and user ID. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | 6 | /// GraphQL query to lookup a user account by type and user ID. 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/users/accounts/lookup.graphql" 11 | )] 12 | pub struct AccountLookup; 13 | 14 | use self::account_lookup::{ResponseData, Variables}; 15 | 16 | impl AccountLookup { 17 | /// Make the variables for an account lookup query. 18 | pub fn make_variables(user_id: uuid, platform: user_account) -> Variables { 19 | Variables { user_id, platform } 20 | } 21 | 22 | /// Send the account lookup query. This return the user's ID on the given platform if there 23 | /// is one linked. 24 | pub async fn send( 25 | user_id: uuid, 26 | platform: user_account, 27 | ) -> Result, TelescopeError> { 28 | // Send the query and convert the response. 29 | send_query::(Self::make_variables(user_id, platform)) 30 | .await 31 | .map(|response| response.platform_id()) 32 | } 33 | } 34 | 35 | impl ResponseData { 36 | /// The id associated with a given RCOS user for a given platform (as specified 37 | /// in the query). 38 | fn platform_id(self) -> Option { 39 | Some(self.user_accounts_by_pk?.account_id) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/api/rcos/users/accounts/mod.rs: -------------------------------------------------------------------------------- 1 | //! RCOS User account queries and mutations. 2 | 3 | pub mod for_user; 4 | pub mod link; 5 | pub mod lookup; 6 | pub mod reverse_lookup; 7 | pub mod unlink; 8 | -------------------------------------------------------------------------------- /src/api/rcos/users/accounts/reverse_lookup.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API query to get the user ID (if available) of a user by platform and account id. 2 | 3 | // Import and rename for GraphQL macro 4 | use crate::api::rcos::prelude::*; 5 | use crate::api::rcos::users::UserAccountType as user_account; 6 | 7 | /// Type representing query for user ID given a platform and user id on that 8 | /// platform. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/users/accounts/reverse_lookup.graphql" 13 | )] 14 | pub struct ReverseLookup; 15 | 16 | use crate::api::rcos::send_query; 17 | use crate::error::TelescopeError; 18 | use reverse_lookup::ResponseData; 19 | use reverse_lookup::Variables; 20 | 21 | impl ReverseLookup { 22 | /// Make the variables for a reverse account lookup. 23 | fn make_vars(platform: user_account, platform_id: String) -> Variables { 24 | Variables { 25 | platform, 26 | id: platform_id, 27 | } 28 | } 29 | 30 | /// Get the user ID associated with an ID on a different platform if available. 31 | pub async fn execute( 32 | platform: user_account, 33 | platform_id: String, 34 | ) -> Result, TelescopeError> { 35 | send_query::(Self::make_vars(platform, platform_id)) 36 | .await 37 | .map(|response| response.user_id()) 38 | } 39 | } 40 | 41 | impl ResponseData { 42 | /// Get the user ID of a user (if they exist) via their 43 | /// account id for a given platform. 44 | fn user_id(mut self) -> Option { 45 | Some(self.user_accounts.pop()?.user_id) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/api/rcos/users/accounts/unlink.rs: -------------------------------------------------------------------------------- 1 | //! Mutation to unlink a user account. 2 | 3 | // Namespace items for generated module 4 | use crate::api::rcos::prelude::*; 5 | use crate::api::rcos::users::UserAccountType as user_account; 6 | 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/users/accounts/unlink.graphql" 11 | )] 12 | pub struct UnlinkUserAccount; 13 | 14 | use crate::api::rcos::send_query; 15 | use crate::error::TelescopeError; 16 | use unlink_user_account::{ResponseData, Variables}; 17 | 18 | impl UnlinkUserAccount { 19 | /// Make variables for an unlink user-account mutation. 20 | fn make_variables(user_id: uuid, platform: user_account) -> Variables { 21 | Variables { user_id, platform } 22 | } 23 | 24 | /// Unlink and delete a user account from the database. Return the platform id 25 | /// of the account if it existed. 26 | /// 27 | /// This should be used with significant care, as a user record in the database with no linked 28 | /// accounts is orphaned and the user will not be able to login and use Telescope. 29 | pub async fn send( 30 | user_id: uuid, 31 | platform: user_account, 32 | ) -> Result, TelescopeError> { 33 | // Send the query, wait for and convert the response 34 | send_query::(Self::make_variables(user_id, platform)) 35 | .await 36 | .map(ResponseData::platform_id) 37 | } 38 | } 39 | 40 | impl ResponseData { 41 | /// Get the on-platform ID of the account deleted. 42 | fn platform_id(self) -> Option { 43 | Some(self.delete_user_accounts_by_pk?.account_id) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/api/rcos/users/create.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to create a user record and user_account record with it. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL mutation to create a user and a user account. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/users/create_one.graphql" 12 | )] 13 | pub struct CreateOneUser; 14 | 15 | impl CreateOneUser { 16 | /// Create a user and return the created user ID if this call did not fail. 17 | pub async fn execute( 18 | first_name: String, 19 | last_name: String, 20 | role: user_role, 21 | platform: user_account, 22 | platform_id: String, 23 | ) -> Result, TelescopeError> { 24 | send_query::(create_one_user::Variables { 25 | first_name, 26 | last_name, 27 | role, 28 | platform, 29 | platform_id, 30 | }) 31 | .await 32 | .map(|response| response.insert_users_one.map(|obj| obj.id)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/rcos/users/delete.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API mutation to delete a user 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | 6 | #[derive(GraphQLQuery)] 7 | #[graphql( 8 | schema_path = "graphql/rcos/schema.json", 9 | query_path = "graphql/rcos/users/delete.graphql" 10 | )] 11 | pub struct DeleteUser; 12 | 13 | use delete_user::{ResponseData, Variables}; 14 | 15 | impl DeleteUser { 16 | pub async fn execute(user_id: uuid) -> Result { 17 | send_query::(Variables { user_id }).await 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/rcos/users/discord_whois.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL query to get user info to populate the embed for the `/whois` command on the 2 | //! RCOS Discord bot. 3 | 4 | use crate::api::rcos::prelude::*; 5 | use crate::api::rcos::send_query; 6 | use crate::error::TelescopeError; 7 | 8 | /// ZST representing the associated GraphQL query. 9 | #[derive(GraphQLQuery)] 10 | #[graphql( 11 | schema_path = "graphql/rcos/schema.json", 12 | query_path = "graphql/rcos/users/discord_whois.graphql", 13 | response_derives = "Debug,Clone,Serialize" 14 | )] 15 | pub struct DiscordWhoIs; 16 | 17 | use discord_who_is::ResponseData; 18 | use discord_who_is::Variables; 19 | 20 | impl DiscordWhoIs { 21 | /// Send this query for a given discord user. 22 | pub async fn send(discord_id: u64) -> Result { 23 | // Construct the query variables 24 | let query_vars = Variables { 25 | discord_id: format!("{}", discord_id), 26 | }; 27 | 28 | // Send the query. 29 | return send_query::(query_vars).await; 30 | } 31 | } 32 | 33 | impl ResponseData { 34 | /// Extract the user data (if there is a user) from the response data. 35 | pub fn get_user(self) -> Option { 36 | // Get the mutable lists of the returned accounts. 37 | let mut accounts: Vec<_> = self.user_accounts; 38 | // Get the last one (there should be at most one). 39 | return accounts 40 | .pop() 41 | // Everything is in the user field. 42 | .map(|account| account.user); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/api/rcos/users/enrollments/edit_enrollment.rs: -------------------------------------------------------------------------------- 1 | //! Meeting edit mutation and host selection query. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::error::TelescopeError; 6 | 7 | /// Type representing GraphQL enrollment edit mutation. 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/users/enrollments/edit_enrollment.graphql", 12 | response_derives = "Debug,Copy,Clone,Serialize" 13 | )] 14 | pub struct EditEnrollment; 15 | 16 | impl EditEnrollment{ 17 | pub async fn execute(vars: edit_enrollment::Variables) -> Result, TelescopeError>{ 18 | send_query::(vars) 19 | .await 20 | .map(|response| response.update_enrollments_by_pk.map(|obj| obj.user_id)) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/api/rcos/users/enrollments/enrollment_by_ids.rs: -------------------------------------------------------------------------------- 1 | use crate::error::TelescopeError; 2 | use crate::api::rcos::{prelude::*, send_query}; 3 | 4 | #[derive(GraphQLQuery)] 5 | #[graphql( 6 | schema_path = "graphql/rcos/schema.json", 7 | query_path = "graphql/rcos/users/enrollments/enrollment_by_ids.graphql", 8 | response_derives = "Debug,Clone,Serialize" 9 | )] 10 | 11 | pub struct EnrollmentByIds; 12 | 13 | impl EnrollmentByIds { 14 | pub async fn get( 15 | user_id: uuid, 16 | semester_id: String, 17 | ) -> Result { 18 | send_query::(enrollment_by_ids::Variables { 19 | semester_id, 20 | user_id, 21 | }) 22 | .await 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/rcos/users/enrollments/enrollments_lookup.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API query to get enrollment record. 2 | 3 | use crate::api::rcos::send_query; 4 | use crate::api::rcos::{prelude::*, search_strings::resolve_search_string}; 5 | use crate::error::TelescopeError; 6 | 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/users/enrollments/enrollments_lookup.graphql", 11 | response_derives = "Debug,Clone,Serialize" 12 | )] 13 | 14 | pub struct EnrollmentsLookup; 15 | 16 | impl EnrollmentsLookup { 17 | pub async fn get( 18 | semester_id: String, 19 | ) -> Result { 20 | send_query::(enrollments_lookup::Variables { 21 | semester_id: semester_id, 22 | }) 23 | .await 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/api/rcos/users/enrollments/mod.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL operations on user enrollments. 2 | 3 | pub mod enrollments_lookup; 4 | pub mod user_enrollment_lookup; 5 | pub mod enrollment_by_ids; 6 | pub mod edit_enrollment; 7 | -------------------------------------------------------------------------------- /src/api/rcos/users/enrollments/user_enrollment_lookup.rs: -------------------------------------------------------------------------------- 1 | //! RCOS API query to get enrollment record. 2 | 3 | use crate::api::rcos::send_query; 4 | use crate::api::rcos::{prelude::*, search_strings::resolve_search_string}; 5 | use crate::error::TelescopeError; 6 | 7 | /// The query returns 20 developers per page. 8 | pub const PER_PAGE: u32 = 20; 9 | 10 | #[derive(GraphQLQuery)] 11 | #[graphql( 12 | schema_path = "graphql/rcos/schema.json", 13 | query_path = "graphql/rcos/users/enrollments/user_enrollment_lookup.graphql", 14 | response_derives = "Debug,Clone,Serialize" 15 | )] 16 | 17 | pub struct UserEnrollmentLookup; 18 | 19 | impl UserEnrollmentLookup { 20 | pub async fn get_by_id( 21 | page_num: u32, 22 | search: Option, 23 | semester_id: String, 24 | ) -> Result { 25 | send_query::(user_enrollment_lookup::Variables { 26 | limit: PER_PAGE as i64, 27 | offset: (PER_PAGE * page_num) as i64, 28 | search: resolve_search_string(search), 29 | semester_id: semester_id, 30 | }) 31 | .await 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/api/rcos/users/profile.rs: -------------------------------------------------------------------------------- 1 | //! Profile query. 2 | 3 | use crate::api::rcos::{prelude::*, send_query}; 4 | use crate::error::TelescopeError; 5 | use chrono::Utc; 6 | 7 | #[derive(GraphQLQuery)] 8 | #[graphql( 9 | schema_path = "graphql/rcos/schema.json", 10 | query_path = "graphql/rcos/users/profile.graphql", 11 | response_derives = "Debug,Clone,Serialize" 12 | )] 13 | pub struct Profile; 14 | 15 | // import generated types. 16 | use profile::{ResponseData, Variables}; 17 | 18 | impl Profile { 19 | /// Get the profile data for a given user ID.. 20 | pub async fn for_user( 21 | target: uuid, 22 | viewer: Option, 23 | ) -> Result { 24 | // Convert viewer to a vec with one or zero user IDs in it. 25 | let viewer = viewer.map(|v| vec![v]).unwrap_or(Vec::new()); 26 | 27 | // Send the query and await the response. 28 | send_query::(Variables { 29 | target, 30 | viewer, 31 | now: Utc::today().naive_utc(), 32 | }) 33 | .await 34 | } 35 | } 36 | 37 | impl ResponseData { 38 | /// Get the target user's Discord ID if available. 39 | pub fn discord(&self) -> Option<&str> { 40 | self.target 41 | .as_ref()? 42 | .discord 43 | .get(0) 44 | .map(|disc| disc.account_id.as_str()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/api/rcos/users/role_lookup.rs: -------------------------------------------------------------------------------- 1 | //! GraphQL lookup to get a user's role. 2 | 3 | use crate::api::rcos::prelude::*; 4 | use crate::api::rcos::send_query; 5 | use crate::api::rcos::users::UserRole; 6 | use crate::error::TelescopeError; 7 | 8 | #[derive(GraphQLQuery)] 9 | #[graphql( 10 | schema_path = "graphql/rcos/schema.json", 11 | query_path = "graphql/rcos/users/role_lookup.graphql", 12 | response_derives = "Debug,Clone,Serialize,Copy" 13 | )] 14 | pub struct RoleLookup; 15 | 16 | impl RoleLookup { 17 | /// Get a user's role. Return `Ok(None)` if there is no user record for this user ID. 18 | pub async fn get(user_id: uuid) -> Result, TelescopeError> { 19 | send_query::(role_lookup::Variables { user_id }) 20 | .await 21 | // Extract the role from the results 22 | .map(|result| result.users_by_pk.map(|u| u.role)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/templates/jumbotron.rs: -------------------------------------------------------------------------------- 1 | use crate::templates::Template; 2 | 3 | /// Create a new jumbotron template. 4 | pub fn new(heading: impl Into, message: impl Into) -> Template { 5 | let mut template = Template::new("jumbotron"); 6 | 7 | template.fields = json!({ 8 | "heading": heading.into(), 9 | "message": message.into() 10 | }); 11 | 12 | return template; 13 | } 14 | -------------------------------------------------------------------------------- /src/templates/static_pages/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::TelescopeError; 2 | use crate::templates::page::Page; 3 | use crate::templates::Template; 4 | use actix_web::HttpRequest; 5 | use futures::future::LocalBoxFuture; 6 | 7 | pub mod sponsors; 8 | 9 | /// A piece of static content. This is just a reference to a 10 | /// handlebars file and some metadata for rendering the page. 11 | pub trait StaticPage { 12 | /// The path to the handlebars file. 13 | const TEMPLATE_NAME: &'static str; 14 | 15 | /// The title of this page. 16 | const PAGE_TITLE: &'static str; 17 | 18 | /// Make the static template that this refers to. 19 | fn template() -> Template { 20 | Template::new(Self::TEMPLATE_NAME) 21 | } 22 | 23 | /// Create a page containing the static content. This is also the actix handler 24 | fn page(req: HttpRequest) -> LocalBoxFuture<'static, Result> { 25 | Box::pin(async move { 26 | // We have to double wrap this future to avoid lifetime constraint issue? 27 | // Or at least adding the async block seems to fix it since it moves the template. 28 | Page::new(&req, Self::PAGE_TITLE, Self::template()).await 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/templates/static_pages/sponsors.rs: -------------------------------------------------------------------------------- 1 | use super::StaticPage; 2 | 3 | /// Zero Sized Type linked to the static sponsors page content. 4 | #[derive(Serialize, Default, Debug, Copy, Clone)] 5 | pub struct SponsorsPage; 6 | 7 | impl StaticPage for SponsorsPage { 8 | const TEMPLATE_NAME: &'static str = "static/sponsors"; 9 | const PAGE_TITLE: &'static str = "RCOS Sponsors"; 10 | } 11 | -------------------------------------------------------------------------------- /src/web/middlewares/mod.rs: -------------------------------------------------------------------------------- 1 | //! Telescope's middlewares. 2 | 3 | pub mod authorization; 4 | pub mod error_rendering; 5 | -------------------------------------------------------------------------------- /src/web/mod.rs: -------------------------------------------------------------------------------- 1 | //! Web services and utilities. 2 | 3 | use reqwest::header::HeaderValue; 4 | 5 | pub mod csrf; 6 | pub mod middlewares; 7 | pub mod services; 8 | 9 | lazy_static! { 10 | static ref TELESCOPE_USER_AGENT: String = 11 | format!("rcos-telescope/{}", env!("CARGO_PKG_VERSION")); 12 | } 13 | 14 | /// Get the telescope User-Agent string. 15 | pub fn telescope_ua() -> HeaderValue { 16 | HeaderValue::from_str(TELESCOPE_USER_AGENT.as_str()) 17 | .expect("Could not make Telescope User-Agent") 18 | } 19 | -------------------------------------------------------------------------------- /src/web/services/index.rs: -------------------------------------------------------------------------------- 1 | //! Module for serving the RCOS homepage. 2 | 3 | use crate::api::rcos::landing_page_stats::LandingPageStatistics; 4 | use crate::error::TelescopeError; 5 | use crate::templates::page::Page; 6 | use crate::templates::Template; 7 | use actix_web::HttpRequest; 8 | 9 | /// Path to the Handlebars file from the templates directory. 10 | const TEMPLATE_PATH: &'static str = "index"; 11 | 12 | /// Service that serves the telescope homepage. 13 | #[get("/")] 14 | pub async fn index(req: HttpRequest) -> Result { 15 | // Get the statistics. 16 | let stats = LandingPageStatistics::get().await?; 17 | // Make and return a template with the statistics. 18 | let mut template = Template::new(TEMPLATE_PATH); 19 | template["stats"] = json!(stats); 20 | return template.in_page(&req, "RCOS").await; 21 | } 22 | -------------------------------------------------------------------------------- /src/web/services/meetings/delete.rs: -------------------------------------------------------------------------------- 1 | //! Services for deleting meetings. 2 | 3 | use crate::api::rcos::meetings::authorization_for::{AuthorizationFor, UserMeetingAuthorization}; 4 | use crate::api::rcos::meetings::delete::DeleteMeeting; 5 | use crate::error::TelescopeError; 6 | use crate::web::services::auth::identity::AuthenticationCookie; 7 | use actix_web::http::header::LOCATION; 8 | use actix_web::web::{Path, ServiceConfig}; 9 | use actix_web::HttpResponse; 10 | 11 | /// Register meeting deletion services. 12 | pub fn register(config: &mut ServiceConfig) { 13 | config.service(delete_meeting); 14 | } 15 | 16 | /// Meeting deletion endpoint. Uses post to prevent inadvertent deletion. 17 | #[post("/meeting/{meeting_id}/delete")] 18 | async fn delete_meeting( 19 | auth: AuthenticationCookie, 20 | Path(meeting_id): Path, 21 | ) -> Result { 22 | // Require that there is a user authenticated. 23 | let user_id = auth.get_user_id_or_error().await?; 24 | // Require that they can delete meetings. 25 | let auth: UserMeetingAuthorization = AuthorizationFor::get(Some(user_id)).await?; 26 | if !auth.can_delete_meetings() { 27 | return Err(TelescopeError::Forbidden); 28 | } 29 | 30 | // Authorized. Delete the meeting and associated attendances. 31 | let api_response = DeleteMeeting::execute(meeting_id).await?; 32 | // Check that there was a meeting delete. 33 | if api_response.delete_meetings_by_pk.is_none() { 34 | return Err(TelescopeError::ise( 35 | "Meeting Deletion did not return meeting ID.", 36 | )); 37 | } 38 | 39 | // Meeting deleted successfully. Redirect user back to meetings page. 40 | Ok(HttpResponse::Found().header(LOCATION, "/meetings").finish()) 41 | } 42 | -------------------------------------------------------------------------------- /src/web/services/meetings/mod.rs: -------------------------------------------------------------------------------- 1 | //! Meetings page and services 2 | 3 | use crate::api::rcos::meetings::authorization_for::{AuthorizationFor, UserMeetingAuthorization}; 4 | use crate::error::TelescopeError; 5 | use crate::web::middlewares::authorization::Authorization; 6 | use actix_web::web::ServiceConfig; 7 | use uuid::Uuid; 8 | 9 | mod create; 10 | mod delete; 11 | mod edit; 12 | mod list; 13 | mod view; 14 | 15 | /// Register calendar related services. 16 | pub fn register(config: &mut ServiceConfig) { 17 | // Meetings list page 18 | list::register(config); 19 | 20 | // Meeting creation services 21 | create::register(config); 22 | 23 | // Meeting edit services. 24 | edit::register(config); 25 | 26 | // Meeting destruction services. 27 | delete::register(config); 28 | 29 | config 30 | // The meeting viewing endpoint must be registered after the meeting creation endpoint, 31 | // so that the ID path doesn't match the create path. 32 | .service(view::meeting); 33 | } 34 | 35 | /// Create an authorization middleware based on a meeting authorization function. 36 | fn make_meeting_auth_middleware bool>( 37 | f: &'static F, 38 | ) -> Authorization { 39 | Authorization::new(move |user_id: Uuid| { 40 | Box::pin(async move { 41 | // Get the user meeting access authorization object. 42 | let auth: UserMeetingAuthorization = AuthorizationFor::get(Some(user_id)).await?; 43 | 44 | // Call the verification function on the access authorization object. 45 | (f)(&auth).then(|| ()).ok_or(TelescopeError::Forbidden) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/web/services/mod.rs: -------------------------------------------------------------------------------- 1 | //! Web services. 2 | 3 | use actix_web::web::ServiceConfig; 4 | 5 | mod admin; 6 | mod coordinate; 7 | pub mod auth; 8 | mod index; 9 | pub mod meetings; 10 | pub mod not_found; 11 | mod projects; 12 | pub mod user; 13 | 14 | /// Register all of the routes to the actix app. 15 | pub fn register(config: &mut ServiceConfig) { 16 | // Register authentication related services 17 | auth::register(config); 18 | 19 | // Register user related services 20 | user::register(config); 21 | 22 | // Calendar related services. 23 | meetings::register(config); 24 | 25 | // Project related services. 26 | projects::register(config); 27 | 28 | // Admin panel services. 29 | admin::register(config); 30 | 31 | // Coordinator panel services. 32 | coordinate::register(config); 33 | 34 | config 35 | // Homepage 36 | .service(index::index); 37 | } 38 | -------------------------------------------------------------------------------- /src/web/services/not_found.rs: -------------------------------------------------------------------------------- 1 | use crate::error::TelescopeError; 2 | use actix_web::HttpResponse; 3 | 4 | // Use HttpResponse here because never type is not yet stable. 5 | /// Respond to all requests with page not found. 6 | /// Used as default service. 7 | pub async fn not_found() -> Result { 8 | Err(TelescopeError::PageNotFound) 9 | } 10 | -------------------------------------------------------------------------------- /src/web/services/projects/list.rs: -------------------------------------------------------------------------------- 1 | //! Project page. 2 | use crate::api::rcos::projects::projects_page; 3 | use crate::error::TelescopeError; 4 | use crate::templates::page::Page; 5 | use crate::templates::Template; 6 | use actix_web::HttpRequest; 7 | 8 | use crate::web::services::auth::identity::Identity; 9 | use crate::api::rcos::projects::authorization_for::{AuthorizationFor, UserProjectAuthorization}; 10 | 11 | const TEMPLATE_PATH: &'static str = "projects/list"; 12 | 13 | #[get("/projects")] 14 | pub async fn get( 15 | req: HttpRequest, 16 | identity: Identity, 17 | ) -> Result { 18 | 19 | 20 | let viewer: Option<_> = identity.get_user_id().await?; 21 | 22 | //get their authorization level 23 | let authorization = AuthorizationFor::get(viewer).await?; 24 | 25 | let projects = projects_page::AllProjects::get(0, None).await?; 26 | let mut template = Template::new(TEMPLATE_PATH); 27 | template.fields = json!({ 28 | "projects": projects.projects, 29 | "authorization": authorization, 30 | }); 31 | 32 | return template.in_page(&req, "RCOS Projects").await; 33 | } 34 | -------------------------------------------------------------------------------- /src/web/services/projects/mod.rs: -------------------------------------------------------------------------------- 1 | //! Services related to project management. 2 | 3 | use actix_web::web::ServiceConfig; 4 | use crate::web::middlewares::authorization::Authorization; 5 | use crate::api::rcos::projects::authorization_for::{AuthorizationFor, UserProjectAuthorization}; 6 | use crate::error::TelescopeError; 7 | use uuid::Uuid; 8 | 9 | mod list; 10 | mod view; 11 | // mod create; 12 | 13 | /// Register project services. 14 | pub fn register(conf: &mut ServiceConfig) { 15 | conf.service(list::get); 16 | conf.service(view::project); 17 | } 18 | 19 | /// Create an authorization middleware based on a project authorization function. 20 | pub fn make_projects_auth_middleware bool>( 21 | f: &'static F, 22 | ) -> Authorization { 23 | Authorization::new(move |user_id: Uuid| { 24 | Box::pin(async move { 25 | // Get the user project access authorization object. 26 | let auth: UserProjectAuthorization = AuthorizationFor::get(Some(user_id)).await?; 27 | 28 | // Call the verification function on the access authorization object. 29 | (f)(&auth).then(|| ()).ok_or(TelescopeError::Forbidden) 30 | }) 31 | }) 32 | } 33 | // 34 | -------------------------------------------------------------------------------- /src/web/services/user/login.rs: -------------------------------------------------------------------------------- 1 | //! Login and logout 2 | 3 | use crate::error::TelescopeError; 4 | use crate::templates::auth; 5 | use crate::templates::page::Page; 6 | use crate::web::services::auth::identity::Identity; 7 | use actix_web::http::header::LOCATION; 8 | use actix_web::{HttpRequest, HttpResponse}; 9 | 10 | #[get("/login")] 11 | /// Login page. Users go here and are presented options to login with a variety 12 | /// of identity providers. 13 | pub async fn login_page(req: HttpRequest) -> Result { 14 | auth::login().in_page(&req, "RCOS Login").await 15 | } 16 | 17 | #[get("/logout")] 18 | /// Logout service. This just logs the user out and then redirects them to the 19 | /// homepage. 20 | pub async fn logout(identity: Identity) -> HttpResponse { 21 | // Forget the user's identity 22 | identity.forget(); 23 | // Redirect the user to the homepage. 24 | HttpResponse::Found().header(LOCATION, "/").finish() 25 | } 26 | -------------------------------------------------------------------------------- /src/web/services/user/mod.rs: -------------------------------------------------------------------------------- 1 | //! Services related to users. 2 | 3 | use actix_web::web::ServiceConfig; 4 | 5 | mod delete; 6 | pub mod developers; 7 | mod join_discord; 8 | mod login; 9 | pub mod profile; 10 | mod register; 11 | 12 | /// Register user related services. 13 | pub fn register(config: &mut ServiceConfig) { 14 | // Developers page. 15 | developers::register_services(config); 16 | 17 | // User profile and settings. 18 | profile::register(config); 19 | 20 | // Everything else 21 | config 22 | // Login related services. 23 | .service(login::login_page) 24 | .service(login::logout) 25 | // Registration related services 26 | .service(register::register_page) 27 | .service(register::finish_registration) 28 | .service(register::submit_registration) 29 | // Discord Gateway 30 | .service(join_discord::handle) 31 | // User Deletion 32 | .service(delete::confirm_delete) 33 | .service(delete::profile_delete); 34 | } 35 | -------------------------------------------------------------------------------- /static/icons/telescope/v2-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v2-black.png -------------------------------------------------------------------------------- /static/icons/telescope/v2-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v2-transparent.png -------------------------------------------------------------------------------- /static/icons/telescope/v2-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v2-white.png -------------------------------------------------------------------------------- /static/icons/telescope/v3-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v3-black.png -------------------------------------------------------------------------------- /static/icons/telescope/v3-transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v3-transparent.png -------------------------------------------------------------------------------- /static/icons/telescope/v3-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/icons/telescope/v3-white.png -------------------------------------------------------------------------------- /static/scripts/script.js: -------------------------------------------------------------------------------- 1 | // on document load. 2 | $(document).ready(function () { 3 | // call feather replace to load all feather icons once the document is loaded. 4 | feather.replace(); 5 | 6 | // for all spinner buttons 7 | $(".btn-spinner").each(function () { 8 | let submit_button = $(this); 9 | // get the parent form. 10 | // set submit to start the loading animation. 11 | submit_button.parents("form:first").submit(function () { 12 | // disable the button 13 | submit_button.prop("disabled", true); 14 | // set to loading animation. 15 | submit_button.html( 16 | `` 17 | ); 18 | }); 19 | }); 20 | 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /static/sponsors/google.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /static/sponsors/hfoss.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/sponsors/hfoss.webp -------------------------------------------------------------------------------- /static/sponsors/osi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rcos/Telescope/3bf1c853cad38a62a052d168f5fc2941fee6af1a/static/sponsors/osi.webp -------------------------------------------------------------------------------- /templates/admin/index.hbs: -------------------------------------------------------------------------------- 1 | {{! Admin panel -- currently just link to manage semester data }} 2 | 3 |
4 |
5 |
6 |
7 |

Semester Records

8 |
9 |
10 | Create and edit semester records. View and export semester stats. 11 |
12 | Manage 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /templates/admin/semesters/forms/feedback.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | Part of the form to show feedback. This is included as a partial and 3 | called with two parameters. 4 | 5 | id: The ID of the feedback div. 6 | issue: The nullable field containing feedback message. 7 | }} 8 | 9 | {{#if issue}} 10 |
11 | {{issue}} 12 |
13 | {{/if}} 14 | -------------------------------------------------------------------------------- /templates/admin/semesters/forms/interactivity.hbs: -------------------------------------------------------------------------------- 1 | {{! 2 | This Handlebars partial is included on form feilds to link to the feedback 3 | div when necessary. Also prefills the value. 4 | 5 | Params: 6 | value: Nullable previously entered value. 7 | issue: Nullable feedback message. 8 | feedback_id: Id of the feedback div. 9 | }} 10 | 11 | {{#if value}} value="{{value}}" {{/if}} 12 | 13 | {{#if issue}} 14 | aria-labelledby="{{feedback_id}}" 15 | class="form-control is-invalid" 16 | {{else}} 17 | class="form-control" 18 | {{/if}} 19 | -------------------------------------------------------------------------------- /templates/auth.hbs: -------------------------------------------------------------------------------- 1 |
2 | 21 |
22 | -------------------------------------------------------------------------------- /templates/coordinate/enrollments/index.hbs: -------------------------------------------------------------------------------- 1 |

{{title}} Enrollments

2 | 3 |
4 |
5 |
6 |
7 | 8 |
9 |
10 | 13 |
14 | 15 |
16 | 17 | {{! Pagination buttons }} 18 | {{> pagination/pagination_bar pagination=pagination prefix=prefix preserved_query_string=preserved_query_string}} 19 | 20 | {{#each data}} 21 |
22 |
23 |

24 | {{user.first_name}} {{user.last_name}} 25 | 26 | {{#unless (and ../is_not_admin user.coordinating)}} 27 | Edit User 28 | {{/unless}} 29 | 30 |

31 | 32 |
33 |
34 | 35 | {{else}} 36 |
37 | Could not find any users matching these parameters. 38 |
39 | {{/each}} 40 | -------------------------------------------------------------------------------- /templates/coordinate/index.hbs: -------------------------------------------------------------------------------- 1 |

Semester Records

2 | 3 | 4 | {{#if data.semesters}} 5 |
6 | 7 | 8 | 9 | 15 | 16 | 17 | {{#each data.semesters as | semesters |}} 18 | 19 | 20 | 21 | 22 | {{#with project_pitches_aggregate.aggregate}} 23 | 28 | {{/with}} 29 | {{#with enrollments_aggregate.aggregate}} 30 | 35 | {{/with}} 36 | 37 | {{/each}} 38 | 39 |
Title 10 | Start 11 | End 12 | Project Pitches 13 | Enrollments 14 |
{{title}}{{format_date start_date}}{{format_date end_date}} 24 | 25 | {{count}} 26 | 27 | 31 | 32 | {{count}} 33 | 34 |
40 |
41 | {{else}} 42 | No current semesters found. 43 | {{/if}} 44 | -------------------------------------------------------------------------------- /templates/jumbotron.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{heading}}

3 |

4 | {{message}} 5 |

6 |
-------------------------------------------------------------------------------- /templates/meetings/link.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

5 | 6 |

7 |
8 |
9 | {{text}}
10 | 11 | {{domain_of url}} 12 | 13 |
14 |
15 | {{! Dummy div to align the text center }} 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /templates/meetings/list.hbs: -------------------------------------------------------------------------------- 1 |

RCOS Meetings

2 | 3 |
4 | {{! Form for users to filter events to a certain range }} 5 |
6 | 7 |
8 |
9 |
10 | From 11 |
12 |
13 | 14 |
15 | 16 | 17 |
18 |
19 |
20 | To 21 |
22 |
23 | 24 | 25 |
26 | 27 | 28 |
29 | 30 | {{#if (or authorization.is_current_coordinator (or (eq authorization.role "faculty_advisor") (eq authorization.role "sysadmin")))}} 31 |
32 | Create Meeting 33 |
34 | {{/if}} 35 |
36 | 37 | {{#each meetings}} 38 | {{> meetings/card this}} 39 | {{else}} 40 | {{! No meetings -- display a message }} 41 |
42 | Could not find any meetings matching these parameters. 43 |
44 | {{/each}} 45 | -------------------------------------------------------------------------------- /templates/meetings/title.hbs: -------------------------------------------------------------------------------- 1 | {{! Template to render a meeting title}} 2 | {{! Italisize draft meetings }} 3 | {{#if is_draft}}{{/if}} 4 | 5 | {{#if title}} 6 | {{title}} 7 | {{else}} 8 | {{format_meeting_type type}} 9 | {{format_date start_date_time}} 10 | {{/if}} 11 | 12 | {{#if is_draft}}{{/if}} -------------------------------------------------------------------------------- /templates/ogp_tags.hbs: -------------------------------------------------------------------------------- 1 | {{#each this}} 2 | {{! Two tabs so HTML formats properly according to page.hbs }} 3 | 4 | {{/each}} 5 | 6 | -------------------------------------------------------------------------------- /templates/pagination/link.hbs: -------------------------------------------------------------------------------- 1 | {{! Template for pagination links. }} 2 |
  • 3 | 4 | {{number}} 5 | 6 |
  • 7 | -------------------------------------------------------------------------------- /templates/pagination/separator.hbs: -------------------------------------------------------------------------------- 1 | {{! Pagination ellipsis }} 2 |
  • 3 | ... 4 |
  • 5 | -------------------------------------------------------------------------------- /templates/projects/card.hbs: -------------------------------------------------------------------------------- 1 | {{! Project card template -- this is used in the project list and on user profiles }} 2 | 3 |
    4 |
    5 | 6 | 7 | 8 |
    9 |
    10 |
    11 | {{title}} 12 |
    13 |
    14 |
    15 |
    16 | {{#if description}} 17 | {{!
    }} 18 | {{render_markdown description}} 19 | {{!
    }} 20 | {{else}} 21 | {{!
    }} 22 | Missing Description 23 | {{!
    }} 24 | {{/if}} 25 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /templates/projects/card_copy.hbs: -------------------------------------------------------------------------------- 1 | {{! Project card template -- this is used in the project list and on user profiles }} 2 | 3 |
    4 |

    6 | {{title}} 7 | 8 | 9 | View Details 10 | 11 | 12 |

    13 | 14 |
    15 | {{#with most_recent_pm}} 16 |
    17 | 18 | Hosted by {{first_name}} {{last_name}} 19 | 20 |
    21 | {{/with}} 22 | 23 | {{#if description}} 24 |
    25 | {{render_markdown description}} 26 |
    27 | {{/if}} 28 |
    29 |
    30 | -------------------------------------------------------------------------------- /templates/projects/list.hbs: -------------------------------------------------------------------------------- 1 |

    Projects

    2 |
    3 | {{#if (or authorization.is_current_coordinator (or (eq authorization.role "faculty_advisor") (eq authorization.role "sysadmin")))}} 4 |
    5 | Create Project 6 |
    7 | {{/if}} 8 |
    9 |
    10 | {{#each projects}} 11 |
    12 | {{> projects/card this}} 13 |
    14 | {{else}} 15 | {{! No meetings -- display a message }} 16 |
    17 | Could not find any projects matching these parameters. 18 |
    19 | {{/each}} 20 |
    21 | -------------------------------------------------------------------------------- /templates/projects/page.hbs: -------------------------------------------------------------------------------- 1 | {{! Project page template }} 2 | 3 |

    {{project.title}}

    4 | 5 | {{! Description Card }} 6 |
    7 |
    8 |
    9 |

    Description

    10 |
    11 | 12 |
    13 | {{#if project.description}} 14 | {{render_markdown project.description}} 15 | {{else}} 16 | 17 | No Description Available. 18 | 19 | {{/if}} 20 |
    21 |
    22 |
    23 | 24 | {{! Stack Card }} 25 |
    26 |
    27 |
    28 |

    Description

    29 |
    30 | 31 |
    32 | {{#if project.description}} 33 | {{render_markdown project.description}} 34 | {{else}} 35 | 36 | No Description Available. 37 | 38 | {{/if}} 39 |
    40 |
    41 |
    42 | -------------------------------------------------------------------------------- /templates/user/delete.hbs: -------------------------------------------------------------------------------- 1 | {{! User settings form. Users can change their name and role here }} 2 | 3 |
    4 |
    5 |
    6 |

    Delete

    7 |
    8 | 9 |
    10 |
    11 | Are you sure you want to delete the profile for {{target.first_name}} {{target.last_name}}? 12 | 13 | This is a permanent action. 14 | 15 | Cancel 16 | 19 |
    20 |
    21 |
    22 |
    23 | --------------------------------------------------------------------------------