├── .env.dev ├── .github └── workflows │ ├── django.yaml │ └── pre-commit.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── LICENSE ├── MIGRATION_COMMANDS ├── README.md ├── apps ├── __init__.py ├── csua_backend │ ├── __init__.py │ ├── middleware.py │ ├── settings.py │ ├── tests.py │ ├── thumbnail_backends.py │ ├── urls.py │ └── wsgi.py ├── db_data │ ├── 0023_datetime_update.py │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── constants.py │ ├── forms.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ └── fetch_calendar.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20161026_0302.py │ │ ├── 0003_auto_20161105_0809.py │ │ ├── 0004_auto_20161105_0813.py │ │ ├── 0005_sponsor.py │ │ ├── 0006_officer_root_staff.py │ │ ├── 0007_officer_tutor_subjects.py │ │ ├── 0008_event.py │ │ ├── 0009_auto_20171026_2330.py │ │ ├── 0009_auto_20171101_0119.py │ │ ├── 0010_merge.py │ │ ├── 0011_auto_20180610_1527.py │ │ ├── 0012_auto_20190210_1637.py │ │ ├── 0013_officer_officer_since.py │ │ ├── 0014_auto_20190715_0236.py │ │ ├── 0015_auto_20191017_1438.py │ │ ├── 0016_auto_20200503_2322.py │ │ ├── 0017_notice.py │ │ ├── 0018_auto_20200723_2120.py │ │ ├── 0019_auto_20200723_2121.py │ │ ├── 0020_auto_20200728_2346.py │ │ ├── 0021_auto_20210515_1807.py │ │ ├── 0022_auto_20250128_1649.py │ │ ├── 0023_auto_20250422_1440.py │ │ └── __init__.py │ ├── models.py │ ├── staff_views.py │ ├── templatetags │ │ ├── __init__.py │ │ ├── calendar_tags.py │ │ ├── custom_filters.py │ │ └── db_data_tags.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── discordbot │ ├── README.md │ ├── __init__.py │ ├── admin.py │ ├── ani_shuffle.py │ ├── annoucements.py │ ├── bot.py │ ├── connect4.py │ ├── cowsay.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_connectfourgame.py │ │ ├── 0003_auto_20210320_0343.py │ │ ├── 0004_auto_20210321_0152.py │ │ ├── 0005_auto_20210520_2001.py │ │ ├── 0006_anishufflegame.py │ │ ├── 0007_anishufflegame_end_time.py │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ ├── discord_register.html │ │ ├── discord_register_confirm.html │ │ ├── discord_register_email.html │ │ └── discord_register_email_sent.html │ ├── tests.py │ ├── tokens.py │ ├── urls.py │ ├── utils.py │ ├── views.py │ └── xkcd.py ├── ldap │ ├── __init__.py │ ├── admin_views.py │ ├── export_ldap.py │ ├── shell_tests │ │ ├── input.py │ │ └── output │ ├── templates │ │ ├── ldap_admin.html │ │ ├── ldap_admin_group.html │ │ ├── ldap_admin_group_checker.html │ │ ├── ldap_admin_user.html │ │ └── ldap_admin_validate.html │ ├── test_helpers.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── main_page │ ├── __init__.py │ ├── templatetags │ │ ├── __init__.py │ │ └── staticthumbnail.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── newuser │ ├── __init__.py │ ├── addMember.py │ ├── config_newuser │ ├── forms.py │ ├── tests.py │ ├── tokens.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── outreach │ ├── templates │ │ └── outreach │ │ │ ├── email_body.html │ │ │ ├── email_divider.html │ │ │ ├── event_detail.html │ │ │ ├── index.html │ │ │ ├── preview.html │ │ │ └── upcoming_events_email.html │ ├── urls.py │ └── views.py ├── password_reset │ ├── README.md │ ├── __init__.py │ ├── tokens.py │ ├── urls.py │ └── views.py ├── slackbot │ ├── README.md │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── client.py │ ├── log.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── tracker │ ├── __init__.py │ ├── admin.py │ ├── client.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180810_0313.py │ ├── 0003_auto_20181026_2013.py │ ├── 0004_auto_20190204_2243.py │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── bootstrap.sh ├── fixtures ├── csua_ldap_entries.json ├── csua_ldap_info.json ├── csua_ldap_schema.json └── db_data-070918.json ├── js └── icons.js ├── ldap_csua_berkeley_edu_interm.cer ├── manage.py ├── media_root └── images │ └── officers │ └── cardigan.jpg ├── package-lock.json ├── package.json ├── poetry.lock ├── pyproject.toml ├── run-server.sh ├── scripts ├── deploy.sh └── id_rsa.enc ├── static ├── css │ ├── common.css │ ├── computing_resources.css │ ├── events.css │ ├── index.css │ ├── officers.css │ └── thirdparty │ │ ├── animate.min.css │ │ └── bulma.min.css ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── icons.js ├── icons.js.LICENSE.txt ├── images │ ├── Ben_and_Starr_at_Calapalooza.jpg │ ├── CSUA.jpg │ ├── PuzzleHunt.jpg │ ├── Woz.jpg │ ├── benevolence.png │ ├── capy_and_laptop.png │ ├── donate1.jpg │ ├── donate2.jpg │ ├── events │ │ ├── fall2013 │ │ │ ├── 3mtechtalk.jpg │ │ │ ├── bbq.png │ │ │ ├── boardgamenight.jpg │ │ │ ├── codingforgrub.jpg │ │ │ ├── gm2.jpg │ │ │ ├── gmone.jpg │ │ │ ├── gradpanel.jpg │ │ │ ├── hackathon.jpg │ │ │ ├── hackathonbar.jpg │ │ │ ├── hackplus.jpg │ │ │ ├── hackplusplus.jpg │ │ │ ├── hknlan.jpg │ │ │ ├── industrypanel.jpg │ │ │ ├── jack.jpg │ │ │ ├── squareworkshop.jpg │ │ │ ├── startupfair.jpg │ │ │ └── zynga.jpg │ │ ├── fall2014 │ │ │ ├── gm1.jpg │ │ │ ├── unixvim.jpg │ │ │ └── welcomebbq.jpg │ │ ├── fall2015 │ │ │ └── hackathonfa15.jpg │ │ └── spring2014 │ │ │ ├── abasocial.jpg │ │ │ ├── alumnibbq.jpg │ │ │ ├── captureflag.jpg │ │ │ ├── crefresher.jpg │ │ │ ├── gm2.jpg │ │ │ ├── gm3.jpg │ │ │ ├── googlecodejam.jpg │ │ │ ├── hackathon.jpg │ │ │ ├── hackathonbanner.jpg │ │ │ ├── hknbplan.jpg │ │ │ ├── intel.jpg │ │ │ ├── merakicfg.jpg │ │ │ ├── oxygencloud.jpg │ │ │ ├── webdesign.jpg │ │ │ ├── welcomesocial.jpg │ │ │ ├── workday.jpg │ │ │ └── wozvegas.jpg │ ├── header.png │ ├── icons │ │ ├── logo.svg │ │ └── twitch.png │ ├── industry.jpg │ ├── join.jpg │ ├── logo.png │ ├── meraki.jpg │ ├── msft.jpg │ ├── multiblox.png │ ├── officers.jpg │ ├── pattern.png │ ├── pb_fa18.jpg │ ├── pb_sp19.jpg │ ├── pic.png │ ├── standing_capy.png │ ├── tushar_photoshoots │ │ ├── 000045330005_compressed.jpg │ │ ├── 000045330006_compressed.jpg │ │ ├── 000045330007_compressed.jpg │ │ ├── 000045330008_compressed.jpg │ │ ├── 000045330009_compressed.jpg │ │ ├── 000045330010_compressed.jpg │ │ ├── 000045330013_compressed.jpg │ │ ├── 000045330014_compressed.jpg │ │ ├── 000045330015_compressed.jpg │ │ ├── Officers_FA16.jpg │ │ └── new_donate.jpg │ └── welcome_social_sp19.jpg ├── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── fb.js │ ├── jquery-1.9.1.js │ ├── jquery.countdown.min.js │ ├── jquery.js │ ├── jquery.plugin.min.js │ └── script.js └── robots.txt ├── templates ├── 404.html ├── add_officer.html ├── alumni.html ├── archives.html ├── base.html ├── base_with_hero.html ├── calendar.html ├── computers.html ├── constitution.html ├── contact.html ├── create_success.html ├── events.html ├── flatpages │ └── default.html ├── form.html ├── hero_with_bg_img.html ├── index.html ├── join.html ├── meeting_minutes.html ├── newuser.html ├── newuser_email.html ├── newuserremoterequest.html ├── officers.html ├── password_reset │ ├── requestpasswordreset.html │ ├── resetpasswordconfirm.html │ └── resetsuccess.html ├── password_reset_email.html ├── politburo.html ├── profile.html ├── registration │ └── login.html ├── simple_page.html ├── sponsors.html ├── sponsors_grid.html ├── tech.html ├── tutoring.html └── workshops.html └── webpack.config.js /.env.dev: -------------------------------------------------------------------------------- 1 | DJANGO_DEBUG=True 2 | NEWUSER_PW=fill this in from /etc/secrets/newuser.secret 3 | DJANGO_FILEBASED_EMAIL_BACKEND=True 4 | GOOGLE_CALENDAR_API_KEY= -------------------------------------------------------------------------------- /.github/workflows/django.yaml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | DJANGO_SECRET_KEY: pnunez1 7 | NEWUSER_PW: pnunez2 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | max-parallel: 4 14 | matrix: 15 | python-version: [3.9] 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Cache poetry install 23 | uses: actions/cache@v4 24 | with: 25 | path: ~/.local 26 | key: poetry-1.1.12-0 27 | - name: Install poetry 28 | uses: snok/install-poetry@v1 29 | with: 30 | virtualenvs-create: true 31 | virtualenvs-in-project: true 32 | - name: Cache dependencies 33 | id: cache-deps 34 | uses: actions/cache@v4 35 | with: 36 | path: .venv 37 | key: pydeps-${{ hashFiles('**/poetry.lock') }} 38 | - name: Install Dependencies 39 | run: | 40 | poetry install --no-interaction --no-root 41 | if: steps.cache-deps.outputs.cache-hit != 'true' 42 | - name: Run Tests 43 | run: | 44 | .venv/bin/python manage.py test 45 | # deploy: 46 | # runs-on: ubuntu-latest 47 | # needs: build 48 | # if: success() && github.ref == 'refs/heads/master' 49 | # steps: 50 | # - uses: actions/checkout@v4 51 | # with: 52 | # sparse-checkout: | 53 | # scripts/deploy.sh 54 | # sparse-checkout-cone-mode: false 55 | # - name: Read deploy script contents 56 | # uses: andstor/file-reader-action@v1.0.0 57 | # id: deploy_script 58 | # with: 59 | # path: scripts/deploy.sh 60 | # - name: Run deployment script if master branch updated 61 | # uses: appleboy/ssh-action@v1.0.3 62 | # with: 63 | # host: ${{ secrets.DEPLOY_HOST }} 64 | # username: ${{ secrets.DEPLOY_USERNAME }} 65 | # key: ${{ secrets.DEPLOY_KEY }} 66 | # port: ${{ secrets.DEPLOY_PORT }} 67 | # script: ${{ steps.deploy_script.outputs.contents }} 68 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yaml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v3 14 | - uses: pre-commit/action@v3.0.1 15 | # vim: set sw=2 ts=2: 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.bak 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | bin/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | htmlcov/ 32 | .tox/ 33 | .coverage 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Mr Developer 42 | .mr.developer.cfg 43 | .project 44 | .pydevproject 45 | 46 | # Rope 47 | .ropeproject 48 | 49 | # Django stuff: 50 | *.log 51 | *.pot 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # No dumb files allowed 57 | .DS_Store 58 | 59 | # Compile static files 60 | static_root 61 | media_root 62 | 63 | # database 64 | csua.sqlite3 65 | data/ 66 | 67 | # Ansible playbook retries 68 | *.retry 69 | 70 | .mypy_cache 71 | .vagrant/ 72 | 73 | /.env 74 | 75 | # Virtual environment 76 | /venv 77 | .vscode/settings.json 78 | node_modules 79 | 80 | /.idea -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 23.1.0 4 | hooks: 5 | - id: black 6 | - repo: https://github.com/pycqa/isort 7 | rev: 5.12.0 8 | hooks: 9 | - id: isort 10 | args: ["--profile", "black", "--filter-files"] 11 | 12 | 13 | exclude: "venv" 14 | # vim: et sw=2 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | # command to run tests 5 | env: 6 | - DJANGO_SECRET_KEY=pnunez1 NEWUSER_PW=pnunez2 7 | script: 8 | - python manage.py test 9 | 10 | # https://oncletom.io/2016/travis-ssh-deploy/ 11 | before_deploy: 12 | - openssl aes-256-cbc -k $CSUA_SECRET_KEY -base64 -in scripts/id_rsa.enc -out /tmp/id_rsa -d 13 | - eval "$(ssh-agent -s)" 14 | - chmod 600 /tmp/id_rsa 15 | - ssh-add /tmp/id_rsa 16 | 17 | deploy: 18 | - provider: script 19 | script: bash scripts/deploy.sh 20 | on: 21 | repo: CSUA/csua-backend 22 | branch: master 23 | # vi: sw=2 ts=2 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Computer Science Undergraduate Association 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. -------------------------------------------------------------------------------- /MIGRATION_COMMANDS: -------------------------------------------------------------------------------- 1 | # Just a bunch of commands I used while migrating stuff around 2 | # I honestly forgot what most of them are for but I find this useful to reference 3 | # -robertq 4 | 5 | cat db_data_08292019.json | jq '[.[] | select(.model == "db_data.politburo")] | del(.[].fields.officer)' > pb.json 6 | 7 | cat db_data_08292019.json | jq '[.[] | select(.model == "db_data.officer")] | [.[].fields | {fields: {user_id: (.first_name + "_" + .last_name), photo1: .photo1, photo2: .photo2}}]' 8 | 9 | insert into auth_user (first_name, last_name, username, is_active) values ("Testf", "Testl", "robertq_test3", 1); 10 | 11 | cat officers.txt | sed -E 's/(\w+) (\w+),(.+)/\"\1\","\2","\3\"/' > officers.csv'"' 12 | 13 | load data local infile 'officers.csv' into table auth_user fields terminated by ',' (first_name, last_name, username); 14 | 15 | delete from auth_user where username like '"%' 16 | 17 | load data local infile 'officers.csv' into table auth_user fields terminated by ',' enclosed by '"' (first_name, last_name, username); 18 | 19 | update auth_user set is_active=1; 20 | 21 | cat person.json | jq -r '.[].fields | .user_id + "," + .photo1 + "," + .photo2' > person.csv 22 | 23 | 24 | while read row 25 | do 26 | fullname="$(echo $row | cut -d ',' -f 1 | tr ' ' '_')"; 27 | username="$(echo $row | cut -d ',' -f 2)"; 28 | sed -i -e "s/$fullname/$username/" person2.csv 29 | done < officers.txt 30 | 31 | delete ignore from db_data_person; 32 | 33 | load data local infile 'person2.csv' into table db_data_person fields terminated by ',' (user_id, photo1, photo2) 34 | 35 | cat db_data_08292019.json | jq -r '.[] | select(.model == "db_data.sponsor") | .fields | [.name, .url, .photo] | @csv' > sponsors.csv 36 | 37 | cat sponsors.csv | tr -d \" > sponsors_no_quote.csv 38 | 39 | sudo -u www-data python3.6 /webserver/CSUA-backend/manage.py dumpdata auth > hue 40 | 41 | cat auth_hue | sed 's/null/"1998-10-12T00:00:00Z"/g' | jq '.' > auth_hue_fix 42 | -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/__init__.py -------------------------------------------------------------------------------- /apps/csua_backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/csua_backend/__init__.py -------------------------------------------------------------------------------- /apps/csua_backend/middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.redirects.middleware import RedirectFallbackMiddleware 3 | from django.contrib.redirects.models import Redirect 4 | from django.contrib.sites.shortcuts import get_current_site 5 | from django.http import HttpResponseRedirect 6 | 7 | 8 | class TemporaryRedirectFallbackMiddleware(RedirectFallbackMiddleware): 9 | """ 10 | This makes it so that django.contrib.redirects uses HTTP 307 Temporary 11 | Redirect rather than HTTP 301 Moved Permanently. 12 | This way redirects can be changed--browsers won't cache them. 13 | 14 | In contrast to the default RedirectFallbackMiddleware, we strip out the 15 | query parameters so that extraneous query parameters don't break redirects. 16 | 17 | E.g. GET /zoom/?fbclid=420 properly redirects using the redirect with old_path="/zoom/" 18 | 19 | Read here: 20 | https://docs.djangoproject.com/en/2.2/ref/contrib/redirects/#middleware 21 | 22 | """ 23 | 24 | # from django/contrib/redirects/middleware.py 25 | def process_response(self, request, response): 26 | # No need to check for a redirect for non-404 responses. 27 | if response.status_code != 404: 28 | return response 29 | 30 | current_site = get_current_site(request) 31 | 32 | r = None 33 | try: 34 | r = Redirect.objects.get(site=current_site, old_path=request.path) 35 | except Redirect.DoesNotExist: 36 | pass 37 | if r is None and settings.APPEND_SLASH and not request.path.endswith("/"): 38 | try: 39 | r = Redirect.objects.get(site=current_site, old_path=request.path + "/") 40 | except Redirect.DoesNotExist: 41 | pass 42 | if r is not None: 43 | if r.new_path == "": 44 | return self.response_gone_class() 45 | return HttpResponseRedirect(r.new_path, status=307) 46 | 47 | # No redirect was found. Return the response. 48 | return response 49 | -------------------------------------------------------------------------------- /apps/csua_backend/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.redirects.models import Redirect 2 | from django.contrib.sites.models import Site 3 | from django.test import Client, TestCase 4 | 5 | 6 | class TestRedirectMiddleware(TestCase): 7 | OLD_PATH = "/facebook/" 8 | NEW_PATH = "https://facebook.com/csua/" 9 | 10 | def setUp(self): 11 | Redirect.objects.create( 12 | old_path=self.OLD_PATH, 13 | new_path=self.NEW_PATH, 14 | site=Site.objects.get_current(), 15 | ) 16 | 17 | def test_redirects(self): 18 | self.makeSureItWorks("/facebook") 19 | self.makeSureItWorks("/facebook/") 20 | self.makeSureItWorks("/facebook?fbclid=poopy") 21 | self.makeSureItWorks("/facebook/?fbclid=stinky") 22 | 23 | def makeSureItWorks(self, url): 24 | response = self.client.get(url) 25 | self.assertRedirects( 26 | response, self.NEW_PATH, status_code=307, fetch_redirect_response=False 27 | ) 28 | -------------------------------------------------------------------------------- /apps/csua_backend/thumbnail_backends.py: -------------------------------------------------------------------------------- 1 | # from http://blog.yawd.eu/2012/seo-friendly-image-names-sorl-thumbnail-and-django/ 2 | import os 3 | import re 4 | from urllib.parse import urlparse 5 | 6 | from django.conf import settings 7 | from django.template.defaultfilters import slugify 8 | from sorl.thumbnail.base import ThumbnailBackend 9 | 10 | 11 | class SEOThumbnailBackend(ThumbnailBackend): 12 | """ 13 | Custom backend for SEO-friendly thumbnail file names/urls. 14 | """ 15 | 16 | def _get_thumbnail_filename(self, source, geometry_string, options): 17 | """ 18 | Computes the destination filename. 19 | """ 20 | try: 21 | basepath = source.storage.path("") 22 | except NotImplementedError: 23 | basepath = source.storage.url("") 24 | 25 | split_path = urlparse(source.name).path.split("/") 26 | split_path.insert(-1, geometry_string) 27 | 28 | # attempt to slugify the filename to make it SEO-friendly 29 | split_name = split_path[-1].split(".") 30 | try: 31 | split_path[-1] = "%s.%s" % ( 32 | slugify(".".join(split_name[:-1])), 33 | split_name[-1], 34 | ) 35 | except: 36 | # on fail keep the original filename 37 | pass 38 | 39 | path = os.sep.join(split_path) 40 | 41 | # if the path already starts with THUMBNAIL_PREFIX do not concatenate the PREFIX 42 | # this way we avoid ending up with a url like /images/images/120x120/my.png 43 | if not path.startswith(settings.THUMBNAIL_PREFIX): 44 | return "%s/%s" % (settings.THUMBNAIL_PREFIX, path) 45 | 46 | return path 47 | -------------------------------------------------------------------------------- /apps/csua_backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.static import static 2 | from django.contrib import admin 3 | from django.urls import include, path 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = [ 8 | path("", include("apps.main_page.urls")), 9 | path("", include("apps.db_data.urls")), 10 | path("ldap/", include("apps.ldap.urls")), 11 | path("outreach/", include("apps.outreach.urls")), 12 | path("admin/", admin.site.urls), 13 | path("newuser/", include("apps.newuser.urls")), 14 | path("computers/", include("apps.tracker.urls")), 15 | path("slack/", include("apps.slackbot.urls")), 16 | path("discord/", include("apps.discordbot.urls")), 17 | path( 18 | "reset-password/", 19 | include("apps.password_reset.urls", namespace="password_reset"), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /apps/csua_backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for csua_backend project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 19 | # if running multiple sites in the same mod_wsgi process. To fix this, use 20 | # mod_wsgi daemon mode with each site in its own daemon process, or use 21 | # os.environ["DJANGO_SETTINGS_MODULE"] = "csua_backend.settings" 22 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "apps.csua_backend.settings") 23 | 24 | # This application object is used by any WSGI server configured to use this 25 | # file. This includes Django's development server, if the WSGI_APPLICATION 26 | # setting points here. 27 | from django.core.wsgi import get_wsgi_application 28 | 29 | application = get_wsgi_application() 30 | 31 | # Apply WSGI middleware here. 32 | # from helloworld.wsgi import HelloWorldApplication 33 | # application = HelloWorldApplication(application) 34 | 35 | # We start discordbot thread here so that it doesn't interfere with other django 36 | # commands such as test and migrate 37 | 38 | from apps.discordbot.bot import csua_bot 39 | 40 | if csua_bot: 41 | csua_bot.thread.start() 42 | -------------------------------------------------------------------------------- /apps/db_data/0023_datetime_update.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.db import migrations, models 4 | from django.db.migrations.operations.special import RunPython 5 | 6 | 7 | def date_to_datetime(apps, schema_editor): 8 | Event = apps.get_model("db_data", "Event") 9 | for event in Event.objects.all(): 10 | d = event.date 11 | event.start_time = datetime(year=d.year, month=d.month, day=d.day) 12 | event.save() 13 | 14 | 15 | class Migration(migrations.Migration): 16 | dependencies = [("db_data", "0020_auto_20200728_2346")] 17 | 18 | operations = [ 19 | migrations.AddField( 20 | model_name="event", name="start_time", field=models.DateTimeField(null=True) 21 | ), 22 | migrations.AddField( 23 | model_name="event", name="end_time", field=models.DateTimeField(null=True) 24 | ), 25 | migrations.RunPython(date_to_datetime), 26 | migrations.RemoveField(model_name="event", name="date"), 27 | migrations.RemoveField(model_name="event", name="time"), 28 | ] 29 | -------------------------------------------------------------------------------- /apps/db_data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/db_data/__init__.py -------------------------------------------------------------------------------- /apps/db_data/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DbDataConfig(AppConfig): 5 | name = "db_data" 6 | -------------------------------------------------------------------------------- /apps/db_data/constants.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | 3 | DAYS_OF_WEEK = ["Mon", "Tues", "Wed", "Thu", "Fri"] 4 | OH_TIMES = [ 5 | "9-10 AM", 6 | "10-11 AM", 7 | "11-12 PM", 8 | "12-1 PM", 9 | "1-2 PM", 10 | "2-3 PM", 11 | "3-4 PM", 12 | "4-5 PM", 13 | "5-6 PM", 14 | "6-7 PM", 15 | ] 16 | 17 | # there must be 10 time slots for the below map to work, modify if adding or removing time slots 18 | OH_TIME_MAP = ( 19 | {hour: None for hour in range(0, 9)} 20 | | {hour: OH_TIMES[hour - 9] for hour in range(9, 19)} 21 | | {hour: None for hour in range(19, 24)} 22 | ) 23 | 24 | OH_CHOICES = [ 25 | (choice, choice) 26 | for choice in [" ".join(oh) for oh in product(DAYS_OF_WEEK, OH_TIMES)] + ["N/A"] 27 | ] 28 | -------------------------------------------------------------------------------- /apps/db_data/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from apps.ldap.utils import user_exists 4 | 5 | from .constants import OH_CHOICES 6 | 7 | 8 | class OfficerCreationForm(forms.Form): 9 | username = forms.CharField(max_length=32) 10 | photo = forms.ImageField(label="Photo 1", required=False) 11 | photo_url = forms.CharField(label="Photo 1 URL", required=False) 12 | photo2 = forms.ImageField(label="Photo 2", required=False) 13 | photo2_url = forms.CharField(label="Photo 2 URL", required=False) 14 | blurb = forms.CharField(widget=forms.Textarea, max_length=255, required=False) 15 | office_hours = forms.ChoiceField( 16 | label="Office Hour", choices=([("", "")] + OH_CHOICES), required=False 17 | ) 18 | officer_since = forms.DateField( 19 | widget=forms.DateInput(attrs={"type": "date"}), required=False 20 | ) 21 | 22 | def clean(self): 23 | cleaned_data = super().clean() 24 | if (not cleaned_data.get("photo")) + (not cleaned_data.get("photo_url")) == 0: 25 | raise forms.ValidationError( 26 | "Please specify up to one of 'Photo 1' or 'Photo 1 URL'" 27 | ) 28 | if (not cleaned_data.get("photo2")) + (not cleaned_data.get("photo2_url")) == 0: 29 | raise forms.ValidationError( 30 | "Please specify up to one of 'Photo 2' or 'Photo 2 URL'" 31 | ) 32 | username = cleaned_data.get("username") 33 | if not user_exists(username): 34 | raise forms.ValidationError(f"User {username} is not in LDAP") 35 | return cleaned_data 36 | -------------------------------------------------------------------------------- /apps/db_data/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/db_data/management/__init__.py -------------------------------------------------------------------------------- /apps/db_data/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/db_data/management/commands/__init__.py -------------------------------------------------------------------------------- /apps/db_data/management/commands/fetch_calendar.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from decouple import config 4 | from django.core.cache import cache 5 | from django.core.management.base import BaseCommand 6 | from googleapiclient.discovery import build 7 | 8 | 9 | class Command(BaseCommand): 10 | help = "Fetch Google Calendar events and cache them" 11 | 12 | def handle(self, *args, **kwargs): 13 | API_KEY = config("GOOGLE_CALENDAR_API_KEY") 14 | CALENDAR_ID = ( 15 | "berkeley.edu_rv641pmt9o13qnh1ss4uib78bs@group.calendar.google.com" 16 | ) 17 | 18 | service = build("calendar", "v3", developerKey=API_KEY) 19 | 20 | now = datetime.datetime.utcnow().isoformat() + "Z" 21 | events_result = ( 22 | service.events() 23 | .list( 24 | calendarId=CALENDAR_ID, 25 | timeMin=now, 26 | maxResults=50, 27 | singleEvents=True, 28 | orderBy="startTime", 29 | ) 30 | .execute() 31 | ) 32 | 33 | events = events_result.get("items", []) 34 | cache.set("calendar_events", events, timeout=3600) # Cache for 1 hour 35 | self.stdout.write(self.style.SUCCESS(f"Fetched {len(events)} events.")) 36 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.9 on 2016-10-08 22:58 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="Officer", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("first_name", models.CharField(max_length=70)), 27 | ("last_name", models.CharField(max_length=70)), 28 | ("office_hours", models.CharField(max_length=70)), 29 | ("photo1_url", models.CharField(max_length=255)), 30 | ("photo2_url", models.CharField(max_length=255)), 31 | ("blurb", models.CharField(max_length=255)), 32 | ("pb_position", models.CharField(max_length=255)), 33 | ], 34 | ) 35 | ] 36 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0002_auto_20161026_0302.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-10-26 10:02 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | from apps import db_data 8 | 9 | 10 | class Migration(migrations.Migration): 11 | dependencies = [("db_data", "0001_initial")] 12 | 13 | operations = [ 14 | migrations.RemoveField(model_name="officer", name="photo1_url"), 15 | migrations.RemoveField(model_name="officer", name="photo2_url"), 16 | migrations.AddField( 17 | model_name="officer", 18 | name="photo1", 19 | field=models.ImageField( 20 | default="images/officers/cardigan.jpg", 21 | max_length=255, 22 | upload_to=db_data.models.person_photo_path, 23 | ), 24 | ), 25 | migrations.AddField( 26 | model_name="officer", 27 | name="photo2", 28 | field=models.ImageField( 29 | blank=True, 30 | max_length=255, 31 | upload_to=db_data.models.person_photo_path_alt, 32 | ), 33 | ), 34 | migrations.AlterField( 35 | model_name="officer", 36 | name="pb_position", 37 | field=models.CharField(blank=True, max_length=255), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0003_auto_20161105_0809.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-11-05 15:09 3 | 4 | 5 | import django.db.models.deletion 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | dependencies = [("db_data", "0002_auto_20161026_0302")] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Politburo", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("position", models.CharField(max_length=30)), 26 | ("title", models.CharField(max_length=30)), 27 | ("description", models.CharField(max_length=255)), 28 | ("contact", models.CharField(max_length=255)), 29 | ], 30 | ), 31 | migrations.RemoveField(model_name="officer", name="pb_position"), 32 | migrations.AddField( 33 | model_name="politburo", 34 | name="officer", 35 | field=models.OneToOneField( 36 | on_delete=django.db.models.deletion.CASCADE, to="db_data.Officer" 37 | ), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0004_auto_20161105_0813.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-11-05 15:13 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0003_auto_20161105_0809")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="politburo", 14 | name="description", 15 | field=models.CharField(max_length=355), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0005_sponsor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-11-05 18:00 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | from apps import db_data 8 | 9 | 10 | class Migration(migrations.Migration): 11 | dependencies = [("db_data", "0004_auto_20161105_0813")] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="Sponsor", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("name", models.CharField(max_length=70)), 27 | ("url", models.CharField(max_length=70)), 28 | ( 29 | "photo", 30 | models.ImageField( 31 | default="images/officers/cardigan.jpg", 32 | max_length=255, 33 | upload_to=db_data.models.sponsor_photo_path, 34 | ), 35 | ), 36 | ("description", models.CharField(max_length=255)), 37 | ("current", models.BooleanField(default=True)), 38 | ], 39 | ) 40 | ] 41 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0006_officer_root_staff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2016-11-06 00:39 3 | 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0005_sponsor")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="officer", 14 | name="root_staff", 15 | field=models.BooleanField(default=False), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0007_officer_tutor_subjects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2017-10-17 23:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0006_officer_root_staff")] 10 | 11 | operations = [ 12 | migrations.AddField( 13 | model_name="officer", 14 | name="tutor_subjects", 15 | field=models.CharField(default="", max_length=255), 16 | preserve_default=False, 17 | ) 18 | ] 19 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0008_event.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2017-10-18 09:18 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0007_officer_tutor_subjects")] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="Event", 14 | fields=[ 15 | ( 16 | "id", 17 | models.AutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("name", models.CharField(max_length=70)), 25 | ("location", models.CharField(max_length=70)), 26 | ("date", models.DateField()), 27 | ("time", models.CharField(max_length=70)), 28 | ("description", models.TextField()), 29 | ("link", models.URLField()), 30 | ], 31 | ) 32 | ] 33 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0009_auto_20171026_2330.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2017-10-27 06:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0008_event")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="officer", 14 | name="tutor_subjects", 15 | field=models.CharField(blank=True, max_length=255), 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0009_auto_20171101_0119.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2017-11-01 08:19 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0008_event")] 10 | 11 | operations = [ 12 | migrations.AlterField(model_name="sponsor", name="url", field=models.URLField()) 13 | ] 14 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0010_merge.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.3 on 2017-11-01 08:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [ 10 | ("db_data", "0009_auto_20171101_0119"), 11 | ("db_data", "0009_auto_20171026_2330"), 12 | ] 13 | 14 | operations = [] 15 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0011_auto_20180610_1527.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-06-10 22:27 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [("db_data", "0010_merge")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="officer", 13 | name="enabled", 14 | field=models.BooleanField(default=True), 15 | ), 16 | migrations.AlterField( 17 | model_name="politburo", 18 | name="officer", 19 | field=models.OneToOneField( 20 | on_delete=django.db.models.deletion.PROTECT, to="db_data.Officer" 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0012_auto_20190210_1637.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2019-02-11 00:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0011_auto_20180610_1527")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="event", 12 | name="enabled", 13 | field=models.BooleanField(default=True), 14 | preserve_default=False, 15 | ), 16 | migrations.AlterField( 17 | model_name="event", name="date", field=models.DateField(null=True) 18 | ), 19 | migrations.AlterField( 20 | model_name="politburo", 21 | name="contact", 22 | field=models.TextField(max_length=255), 23 | ), 24 | migrations.AlterField( 25 | model_name="politburo", name="description", field=models.TextField() 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0013_officer_officer_since.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.7 on 2019-03-11 02:00 2 | 3 | import django.utils.datetime_safe 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [("db_data", "0012_auto_20190210_1637")] 9 | 10 | operations = [ 11 | migrations.AddField( 12 | model_name="officer", 13 | name="officer_since", 14 | field=models.DateField(default=django.utils.datetime_safe.date.today), 15 | preserve_default=False, 16 | ) 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0015_auto_20191017_1438.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-10-17 21:38 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | dependencies = [("db_data", "0014_auto_20190715_0236")] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="event", 14 | name="category", 15 | field=models.ForeignKey( 16 | blank=True, 17 | help_text="Currently unused.", 18 | null=True, 19 | on_delete=django.db.models.deletion.PROTECT, 20 | to="db_data.EventCategory", 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="officer", 25 | name="person", 26 | field=models.OneToOneField( 27 | help_text="There is one Officer object<->one Person object<->one auth.User<->one LDAP user", 28 | on_delete=django.db.models.deletion.PROTECT, 29 | to="db_data.Person", 30 | ), 31 | ), 32 | migrations.AlterField( 33 | model_name="person", 34 | name="user", 35 | field=models.OneToOneField( 36 | help_text="There is one Officer object<->one Person object<->one auth.User<->one LDAP user", 37 | on_delete=django.db.models.deletion.PROTECT, 38 | primary_key=True, 39 | serialize=False, 40 | to=settings.AUTH_USER_MODEL, 41 | to_field="username", 42 | ), 43 | ), 44 | migrations.AlterField( 45 | model_name="semester", 46 | name="current", 47 | field=models.BooleanField( 48 | help_text="There should only be one current semester." 49 | ), 50 | ), 51 | migrations.AlterField( 52 | model_name="semester", 53 | name="events", 54 | field=models.ManyToManyField( 55 | blank=True, help_text="Currently unused.", to="db_data.Event" 56 | ), 57 | ), 58 | migrations.AlterField( 59 | model_name="semester", 60 | name="id", 61 | field=models.CharField( 62 | help_text="Used for URLs etc.", 63 | max_length=8, 64 | primary_key=True, 65 | serialize=False, 66 | ), 67 | ), 68 | migrations.AlterField( 69 | model_name="semester", 70 | name="name", 71 | field=models.CharField(help_text="Display name", max_length=16), 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0016_auto_20200503_2322.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.9 on 2020-05-04 06:22 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0015_auto_20191017_1438")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="semester", 12 | name="id", 13 | field=models.CharField( 14 | help_text="Used for URLs, should match /(fa|sp)[0-9]{2}/", 15 | max_length=8, 16 | primary_key=True, 17 | serialize=False, 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0017_notice.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-06-13 07:59 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0016_auto_20200503_2322")] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name="Notice", 12 | fields=[ 13 | ( 14 | "id", 15 | models.AutoField( 16 | auto_created=True, 17 | primary_key=True, 18 | serialize=False, 19 | verbose_name="ID", 20 | ), 21 | ), 22 | ( 23 | "text", 24 | models.TextField(help_text="Markdown for the text of the notice"), 25 | ), 26 | ("expires", models.DateField()), 27 | ], 28 | ) 29 | ] 30 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0018_auto_20200723_2120.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-07-24 04:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0017_notice")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="officer", 12 | name="officer_since", 13 | field=models.DateField(blank=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0019_auto_20200723_2121.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-07-24 04:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0018_auto_20200723_2120")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="officer", 12 | name="officer_since", 13 | field=models.DateField(null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0020_auto_20200728_2346.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-07-29 06:46 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("db_data", "0019_auto_20200723_2121")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="officer", 12 | name="officer_since", 13 | field=models.DateField(blank=True, null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0021_auto_20210515_1807.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | def date_to_datetime(apps, schema_editor): 7 | Event = apps.get_model("db_data", "Event") 8 | for event in Event.objects.all(): 9 | d = event.date 10 | event.start_time = datetime(year=d.year, month=d.month, day=d.day) 11 | event.save() 12 | 13 | 14 | class Migration(migrations.Migration): 15 | dependencies = [("db_data", "0020_auto_20200728_2346")] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | model_name="event", name="start_time", field=models.DateTimeField(null=True) 20 | ), 21 | migrations.AddField( 22 | model_name="event", name="end_time", field=models.DateTimeField(null=True) 23 | ), 24 | migrations.RunPython(date_to_datetime), 25 | migrations.RemoveField(model_name="event", name="date"), 26 | migrations.RemoveField(model_name="event", name="time"), 27 | ] 28 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0022_auto_20250128_1649.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2025-01-29 00:49 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("db_data", "0021_auto_20210515_1807"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="officership", 14 | name="blurb", 15 | field=models.CharField(blank=True, max_length=255), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/0023_auto_20250422_1440.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2025-04-22 21:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("db_data", "0022_auto_20250128_1649"), 9 | ] 10 | 11 | operations = [ 12 | migrations.AlterField( 13 | model_name="sponsorship", 14 | name="description", 15 | field=models.CharField(blank=True, max_length=255), 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /apps/db_data/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/db_data/migrations/__init__.py -------------------------------------------------------------------------------- /apps/db_data/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/db_data/templatetags/__init__.py -------------------------------------------------------------------------------- /apps/db_data/templatetags/calendar_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.core.cache import cache 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.inclusion_tag("calendar.html") 8 | def calendar(): 9 | events = cache.get("calendar_events", []) 10 | return {"events": events} 11 | -------------------------------------------------------------------------------- /apps/db_data/templatetags/custom_filters.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django import template 4 | 5 | register = template.Library() 6 | 7 | 8 | @register.filter 9 | def index(list, i): 10 | return list[i] if i < len(list) else None 11 | 12 | 13 | @register.filter 14 | def parse_iso(value): 15 | """ 16 | Convert an ISO formatted date/time string to a datetime object. 17 | Returns None if conversion fails. 18 | """ 19 | try: 20 | return datetime.fromisoformat(value) 21 | except Exception: 22 | return None 23 | 24 | 25 | @register.filter(is_safe=True) 26 | def label_with_classes(value, arg): 27 | return value.label_tag(attrs={"class": arg}) 28 | -------------------------------------------------------------------------------- /apps/db_data/templatetags/db_data_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.utils import timezone 3 | 4 | from apps.db_data.models import Event, Notice 5 | 6 | register = template.Library() 7 | 8 | 9 | @register.simple_tag 10 | def get_upcoming_events(): 11 | return Event.objects.filter(start_time__gte=timezone.now()) 12 | 13 | 14 | @register.simple_tag 15 | def get_current_notices(): 16 | return Notice.objects.filter(expires__gte=timezone.now().date()) 17 | -------------------------------------------------------------------------------- /apps/db_data/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.views.generic.base import TemplateView 3 | 4 | from . import staff_views, views 5 | 6 | urlpatterns = [ 7 | path("politburo/", views.politburo, name="politburo"), 8 | path("politburo/", views.politburo, name="politburo_semester"), 9 | path("officers/", views.officers, name="officers"), 10 | path("add-officer/", staff_views.update_or_create_officer, name="add-officer"), 11 | path("time-machine/", staff_views.update_semester, name="update_semester"), 12 | path("officers//", views.officers, name="officers_semester"), 13 | path("sponsors/", views.sponsors, name="sponsors"), 14 | path("events/", TemplateView.as_view(template_name="events.html"), name="events"), 15 | path( 16 | "events/workshops/", 17 | TemplateView.as_view(template_name="workshops.html"), 18 | name="workshops", 19 | ), 20 | path("tutoring/", views.tutoring, name="tutoring"), 21 | path("archives/", views.archives, name="archives"), 22 | ] 23 | -------------------------------------------------------------------------------- /apps/discordbot/README.md: -------------------------------------------------------------------------------- 1 | # Phillip documentation 2 | 3 | Phillip is a discord bot for CSUA. 4 | 5 | It verifies users' berkeley.edu email addresses and gives them the hoser role. 6 | 7 | CSUA Discord Dev Team: https://discord.com/developers/teams/738580852191264826/information 8 | 9 | * Ask robertq or rnithin if you need to be added. 10 | 11 | Phillip App: https://discord.com/developers/applications/737930184837300274/information 12 | 13 | # How to develop 14 | 15 | For your developer machine, you will need to create a new discord app. Go to https://discord.com/developers/applications and click "New Application". Once it's created, go to the "Bot" tab on the left and create a bot user. Copy the token and paste it in your `.env` file. It should look something like: 16 | 17 | ``` 18 | DISCORD_TOKEN=Nzg2MTkyMDg4MjkwMDMzNjg0.X9C0cA.KYc0aow44Mkjel4nlscoFDKmoC4 19 | ``` 20 | 21 | Next, go to the "Bot" tab. Under "Privledged Gateway Intents", make sure to authorize 22 | both "Presence Intent" and "Server Members Intent". These permissions are required 23 | for Phillip to work. 24 | 25 | Now, go to the "OAuth2" tab. Under the OAuth2 URL generator, select the "bot" scope. 26 | Then, select the bot permissions. Phillip also needs these permissions to work. 27 | 28 | * Send Messages 29 | * Manage Roles 30 | 31 | Then, copy the URL and paste it into your browser. You will probably want to create a test server to test the bot with. 32 | 33 | Testing certain things locally can be tricky. 34 | If testing the email, be sure to set `DJANGO_FILEBASED_EMAIL_BACKEND=True` in your `.env`. 35 | Email you send locally will be in `csua-backend/emails`. 36 | 37 | Once you're happy with your changes to the bot behavior, get your code into the master branch. 38 | Once it's there, then PhilBot will be updated automatically! 39 | -------------------------------------------------------------------------------- /apps/discordbot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/discordbot/__init__.py -------------------------------------------------------------------------------- /apps/discordbot/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import AniShuffleGame, ConnectFourGame, DiscordRegisteredUser 4 | 5 | 6 | # Register your models here. 7 | @admin.register(DiscordRegisteredUser) 8 | class DiscordRegisteredUserAdmin(admin.ModelAdmin): 9 | list_display = ("email", "discord_tag", "timestamp") 10 | 11 | 12 | @admin.register(ConnectFourGame) 13 | class ConnectFourGameAdmin(admin.ModelAdmin): 14 | pass 15 | 16 | 17 | @admin.register(AniShuffleGame) 18 | class AniShuffleGameAdmin(admin.ModelAdmin): 19 | pass 20 | -------------------------------------------------------------------------------- /apps/discordbot/annoucements.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from enum import Enum 3 | 4 | from django.utils import timezone 5 | 6 | from apps.db_data.models import Event 7 | 8 | 9 | class AnnouncementType(Enum): 10 | WEEK = "week" 11 | TOMORROW = "tomorrow" 12 | TODAY = "today" 13 | HOUR = "hour" 14 | B_TIME = "now" # Berkeley Time 15 | 16 | 17 | def get_events_in_time_delta(requested_atype: AnnouncementType): 18 | """ 19 | Retrieves a list of Event objects within the requested 20 | time delta. 21 | 22 | requested_atype will take in enum constants in 23 | AnnouncementType 24 | """ 25 | now = timezone.now().astimezone(timezone.get_current_timezone()) 26 | events = get_events_in_time_range(*timeify(requested_atype)) 27 | return events 28 | 29 | 30 | def get_events_in_time_range(start_time, end_time): 31 | """ 32 | Takes in a two datetime objects, start_time and end_time 33 | Returns events within the datetime range 34 | """ 35 | events = Event.objects.filter(start_time__gte=start_time).filter( 36 | start_time__lte=end_time 37 | ) 38 | return events 39 | 40 | 41 | def timeify(requested_atype: AnnouncementType): 42 | """ 43 | Converts requested_tdelta string into a corresponding datetime object 44 | """ 45 | now = timezone.now() 46 | time_ranges = { 47 | AnnouncementType.WEEK: (now, now + datetime.timedelta(weeks=1)), 48 | AnnouncementType.TOMORROW: ( 49 | now + datetime.timedelta(days=1), 50 | now + datetime.timedelta(days=1, hours=23, minutes=59, seconds=59), 51 | ), 52 | AnnouncementType.TODAY: ( 53 | now, 54 | now + datetime.timedelta(hours=23, minutes=59, seconds=59), 55 | ), 56 | AnnouncementType.HOUR: ( 57 | now + datetime.timedelta(hours=1), 58 | now + datetime.timedelta(hours=1, minutes=59, seconds=59), 59 | ), 60 | AnnouncementType.B_TIME: (now, now + datetime.timedelta(minutes=10)), 61 | } 62 | return time_ranges[requested_atype] 63 | -------------------------------------------------------------------------------- /apps/discordbot/cowsay.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | import discord 4 | from cowpy import cow 5 | 6 | 7 | async def handle(message: discord.Message, cowtype="default"): 8 | text = message.content.split(" ", 1)[1] 9 | wrapper = textwrap.TextWrapper(width=40, drop_whitespace=False) 10 | wrapped_text = "\n".join(wrapper.fill(line) for line in text.split("\n")) 11 | moo = cow.get_cow(cowtype)() 12 | cowtext = moo.milk(wrapped_text) 13 | if len(cowtext) > 1994: 14 | await message.channel.send("!cowsay: Message too long") 15 | return 16 | await message.channel.send(f"```{cowtext}```") 17 | -------------------------------------------------------------------------------- /apps/discordbot/forms.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django import forms 4 | 5 | from .models import DiscordRegisteredUser 6 | 7 | discord_tag_regex = re.compile(r".{2,32}#[0-9]{4}") 8 | 9 | 10 | class DiscordRegisterForm(forms.Form): 11 | email = forms.EmailField( 12 | widget=forms.EmailInput(attrs={"placeholder": "pnunez@berkeley.edu"}) 13 | ) 14 | discord_tag = forms.CharField( 15 | label="Discord Tag", 16 | widget=forms.TextInput(attrs={"placeholder": "pnunez#1337"}), 17 | ) 18 | 19 | def clean(self): 20 | cleaned_data = super().clean() 21 | 22 | errors = [] 23 | email = cleaned_data.get("email") 24 | if not email.endswith("@berkeley.edu"): 25 | errors.append( 26 | "You must use your @berkeley.edu email. " 27 | "If you do not yet have one, please contact " 28 | "jonathan@csua.berkeley.edu." 29 | ) 30 | 31 | discord_tag = cleaned_data.get("discord_tag") 32 | if not discord_tag_regex.fullmatch(discord_tag) or discord_tag.count("#") != 1: 33 | errors.append("Invalid Discord tag") 34 | 35 | if DiscordRegisteredUser.objects.filter(email=email).exists(): 36 | errors.append("Email already registered") 37 | if DiscordRegisteredUser.objects.filter(discord_tag=discord_tag).exists(): 38 | errors.append("Discord Tag already registered") 39 | 40 | if errors: 41 | raise forms.ValidationError(errors) 42 | 43 | return cleaned_data 44 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.12 on 2020-07-30 06:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [] 10 | 11 | operations = [ 12 | migrations.CreateModel( 13 | name="DiscordRegisteredUser", 14 | fields=[ 15 | ( 16 | "id", 17 | models.AutoField( 18 | auto_created=True, 19 | primary_key=True, 20 | serialize=False, 21 | verbose_name="ID", 22 | ), 23 | ), 24 | ("discord_tag", models.CharField(max_length=40)), 25 | ("email", models.EmailField(max_length=254)), 26 | ("timestamp", models.DateTimeField(auto_now_add=True)), 27 | ], 28 | ) 29 | ] 30 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0002_connectfourgame.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-03-05 03:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name="ConnectFourGame", 12 | fields=[ 13 | ("message_id", models.IntegerField(primary_key=True, serialize=False)), 14 | ("player1", models.IntegerField()), 15 | ("player2", models.IntegerField()), 16 | ("is_player1_turn", models.BooleanField(default=True)), 17 | ("winner", models.IntegerField(blank=True, null=True)), 18 | ("state", models.CharField(max_length=255)), 19 | ], 20 | ) 21 | ] 22 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0003_auto_20210320_0343.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-03-20 10:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0002_connectfourgame")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="connectfourgame", name="state", field=models.TextField() 12 | ) 13 | ] 14 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0004_auto_20210321_0152.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-03-21 08:52 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0003_auto_20210320_0343")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="connectfourgame", 12 | name="message_id", 13 | field=models.BigIntegerField(primary_key=True, serialize=False), 14 | ), 15 | migrations.AlterField( 16 | model_name="connectfourgame", name="player1", field=models.BigIntegerField() 17 | ), 18 | migrations.AlterField( 19 | model_name="connectfourgame", name="player2", field=models.BigIntegerField() 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0005_auto_20210520_2001.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.19 on 2021-05-21 03:01 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0004_auto_20210321_0152")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="connectfourgame", 12 | name="player1", 13 | field=models.BigIntegerField(help_text="Discord User ID of player 1"), 14 | ), 15 | migrations.AlterField( 16 | model_name="connectfourgame", 17 | name="player2", 18 | field=models.BigIntegerField(help_text="Discord User ID of player 2"), 19 | ), 20 | migrations.AlterField( 21 | model_name="connectfourgame", 22 | name="winner", 23 | field=models.IntegerField( 24 | blank=True, 25 | help_text="Null if no winner, 1 or 2 if winner exists", 26 | null=True, 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0006_anishufflegame.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2023-02-02 20:40 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0005_auto_20210520_2001")] 8 | 9 | operations = [ 10 | migrations.CreateModel( 11 | name="AniShuffleGame", 12 | fields=[ 13 | ( 14 | "message_id", 15 | models.BigIntegerField(primary_key=True, serialize=False), 16 | ), 17 | ("player_id", models.BigIntegerField()), 18 | ("state", models.CharField(max_length=16)), 19 | ("moves", models.IntegerField(default=0)), 20 | ("start_time", models.DateTimeField(auto_now_add=True)), 21 | ("shuffle_depth", models.IntegerField(null=True)), 22 | ], 23 | ) 24 | ] 25 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/0007_anishufflegame_end_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.28 on 2023-02-02 22:07 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("discordbot", "0006_anishufflegame")] 8 | 9 | operations = [ 10 | migrations.AddField( 11 | model_name="anishufflegame", 12 | name="end_time", 13 | field=models.DateTimeField(null=True), 14 | ) 15 | ] 16 | -------------------------------------------------------------------------------- /apps/discordbot/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/discordbot/migrations/__init__.py -------------------------------------------------------------------------------- /apps/discordbot/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | 6 | class DiscordRegisteredUser(models.Model): 7 | discord_tag = models.CharField(max_length=40) 8 | email = models.EmailField() 9 | timestamp = models.DateTimeField(auto_now_add=True) 10 | 11 | 12 | class ConnectFourGame(models.Model): 13 | message_id = models.BigIntegerField(primary_key=True) 14 | player1 = models.BigIntegerField(help_text="Discord User ID of player 1") 15 | player2 = models.BigIntegerField(help_text="Discord User ID of player 2") 16 | is_player1_turn = models.BooleanField(default=True) 17 | winner = models.IntegerField( 18 | null=True, blank=True, help_text="Null if no winner, 1 or 2 if winner exists" 19 | ) 20 | state = models.TextField() 21 | 22 | 23 | class AniShuffleGame(models.Model): 24 | message_id = models.BigIntegerField(primary_key=True) 25 | player_id = models.BigIntegerField() 26 | state = models.CharField(max_length=16) 27 | moves = models.IntegerField(default=0) 28 | start_time = models.DateTimeField(auto_now_add=True) 29 | end_time = models.DateTimeField(null=True) 30 | shuffle_depth = models.IntegerField(null=True) 31 | -------------------------------------------------------------------------------- /apps/discordbot/templates/discord_register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Discord Account Registration{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Register your Discord Account with CSUA

7 | 8 |

9 | Gain access by registering with your berkeley.edu email. 10 |

11 |
12 |
13 |
14 | {% csrf_token %} 15 | 16 | {{ form.as_table }} 17 |
18 | 19 |
20 |
21 |
22 | {% endblock %} 23 | {# vim:set et sw=4: #} 24 | -------------------------------------------------------------------------------- /apps/discordbot/templates/discord_register_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Discord Registration Complete{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Complete Discord Account Registration

7 | 8 |

9 |

10 |
11 |
12 | {% if messages %} 13 |
    14 | {% for message in messages %} 15 |
  • {{ message.tags|upper }} {{ message }}
  • 16 | {% endfor %} 17 |
18 | {% endif %} 19 |
20 | {% csrf_token %} 21 | 22 |
23 |
24 |
25 | {% endblock %} 26 | {# vim:set et sw=4: #} 27 | -------------------------------------------------------------------------------- /apps/discordbot/templates/discord_register_email.html: -------------------------------------------------------------------------------- 1 | {# comment so that vim detects htmldjango #} 2 | 3 | 4 |

5 | Hello, 6 |

7 |

8 | Someone requested to register their Discord account ({{ discord_tag }}) with your email ({{ email }}). 9 | If this is correct, please click the link below. If not, you can ignore this email. 10 |
11 | {% url 'discord_register_confirm' emailb64=emailb64 discord_tagb64=discord_tagb64 token=token as path %} 12 | {% with host|add:path as fullurl %} 13 | {{ fullurl }} 14 |

15 |

16 | Thank you,
17 | CSUA Discord Moderation Staff 18 |

19 | {% endwith %} 20 | 21 | 22 | {# vim:set et sw=2 ts=2: #} 23 | -------------------------------------------------------------------------------- /apps/discordbot/templates/discord_register_email_sent.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Email Sent{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Email Sent

7 | 8 |

9 | An email was sent to {{ email }}. 10 |

11 |
12 |
13 | {% endblock %} 14 | {# vim:set et sw=4: #} 15 | -------------------------------------------------------------------------------- /apps/discordbot/tokens.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | from django.utils.crypto import salted_hmac 3 | from django.utils.http import int_to_base36 4 | 5 | from apps.ldap.utils import get_user_hashed_password 6 | 7 | 8 | class DiscordTokenGenerator(PasswordResetTokenGenerator): 9 | def _make_hash_value(self, user, timestamp): 10 | return str(user) 11 | 12 | def _make_token_with_timestamp(self, user, timestamp): 13 | ts_b36 = int_to_base36(timestamp) 14 | print(timestamp) 15 | 16 | hash = salted_hmac( 17 | self.key_salt, self._make_hash_value(user, timestamp) 18 | ).hexdigest()[::2] 19 | return "%s-%s" % (ts_b36, hash) 20 | 21 | 22 | discord_token_generator = DiscordTokenGenerator() 23 | -------------------------------------------------------------------------------- /apps/discordbot/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("register/", views.register, name="discord_register"), 7 | path( 8 | "register-sent//", 9 | views.email_sent, 10 | name="discord_register_email_sent", 11 | ), 12 | path( 13 | "register-confirm////", 14 | views.register_confirm, 15 | name="discord_register_confirm", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /apps/discordbot/utils.py: -------------------------------------------------------------------------------- 1 | from django.core.mail import send_mail 2 | from django.template.loader import render_to_string 3 | from django.utils.html import strip_tags 4 | from django.utils.http import urlsafe_base64_encode as b64_encode 5 | 6 | from .tokens import discord_token_generator 7 | 8 | 9 | def send_verify_mail(email, discord_tag, host="csua.berkeley.edu"): 10 | print("Email:", email) 11 | print("Tag:", discord_tag) 12 | emailb64 = b64_encode(email.encode("utf8")) 13 | discord_tagb64 = b64_encode(discord_tag.encode("utf8")) 14 | 15 | token = discord_token_generator.make_token((email, discord_tag)) 16 | html_message = render_to_string( 17 | "discord_register_email.html", 18 | { 19 | "host": host, 20 | "token": token, 21 | "email": email, 22 | "discord_tag": discord_tag, 23 | "emailb64": emailb64, 24 | "discord_tagb64": discord_tagb64, 25 | }, 26 | ) 27 | print("sending email from noreply@csua.berkeley.edu to ", email) 28 | send_mail( 29 | subject="CSUA Discord Email Verification", 30 | message=strip_tags(html_message), 31 | from_email="noreply@csua.berkeley.edu", 32 | recipient_list=[email], 33 | html_message=html_message, 34 | ) 35 | -------------------------------------------------------------------------------- /apps/discordbot/views.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import threading 4 | 5 | from django.contrib import messages 6 | from django.core.mail import send_mail 7 | from django.shortcuts import redirect, render 8 | from django.template.loader import render_to_string 9 | from django.urls import reverse 10 | from django.utils.html import strip_tags 11 | from django.utils.http import urlsafe_base64_decode as b64_decode 12 | from django.utils.http import urlsafe_base64_encode as b64_encode 13 | 14 | from .bot import csua_bot 15 | from .forms import DiscordRegisterForm 16 | from .models import DiscordRegisteredUser 17 | from .tokens import discord_token_generator 18 | from .utils import send_verify_mail 19 | 20 | 21 | def register(request): 22 | if request.method == "POST": 23 | form = DiscordRegisterForm(request.POST) 24 | if form.is_valid(): 25 | email = form.cleaned_data.get("email") 26 | discord_tag = form.cleaned_data.get("discord_tag") 27 | host = request.get_host() 28 | emailb64 = b64_encode(email.encode("utf8")) 29 | send_verify_mail(email, discord_tag, host) 30 | return redirect( 31 | reverse("discord_register_email_sent", kwargs={"emailb64": emailb64}) 32 | ) 33 | else: 34 | form = DiscordRegisterForm() 35 | return render(request, "discord_register.html", {"form": form}) 36 | 37 | 38 | def email_sent(request, emailb64): 39 | email = b64_decode(emailb64).decode("utf8") 40 | return render(request, "discord_register_email_sent.html", {"email": email}) 41 | 42 | 43 | def register_confirm(request, emailb64, discord_tagb64, token): 44 | try: 45 | discord_tag = b64_decode(discord_tagb64).decode("utf8") 46 | email = b64_decode(emailb64).decode("utf8") 47 | except UnicodeDecodeError: 48 | return redirect("/") 49 | errors = False 50 | if discord_token_generator.check_token((email, discord_tag), token): 51 | if request.method == "POST": 52 | if DiscordRegisteredUser.objects.filter(email=email).exists(): 53 | messages.error(request, f"{email} already registered!") 54 | errors = True 55 | 56 | if DiscordRegisteredUser.objects.filter(discord_tag=discord_tag).exists(): 57 | messages.error(request, f"{discord_tag} already registered!") 58 | errors = True 59 | 60 | if not errors: 61 | if csua_bot.promote_user_to_hoser(discord_tag): 62 | DiscordRegisteredUser.objects.create( 63 | email=email, discord_tag=discord_tag 64 | ) 65 | messages.info( 66 | request, 67 | f"{discord_tag} was successfully registered with email {email}", 68 | ) 69 | else: 70 | messages.error( 71 | request, 72 | f"Failed to register {discord_tag}. " 73 | "Please check for any typos. " 74 | "If you still have problems, please contact the moderators.", 75 | ) 76 | else: 77 | messages.error(request, "Bad Token!") 78 | return render(request, "discord_register_confirm.html", {}) 79 | -------------------------------------------------------------------------------- /apps/discordbot/xkcd.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | 4 | import discord 5 | import requests 6 | 7 | HOST = "http://xkcd.com/" 8 | VALID_XKCD_COMMANDS = [ 9 | "--random", 10 | "-r", 11 | "--help", 12 | "-h", 13 | "--issue", 14 | "-i", 15 | "--current", 16 | "-c", 17 | ] 18 | MAX_ARGUMENT_LENGTH = 3 19 | MIN_ARGUMENT_LENGTH = 2 20 | 21 | 22 | def is_valid_xkcd_command(msg): 23 | """Returns whether msg contains an valid request.""" 24 | arguments = msg.split() 25 | if len(arguments) > MAX_ARGUMENT_LENGTH or len(arguments) < MIN_ARGUMENT_LENGTH: 26 | return False 27 | elif arguments[1] in VALID_XKCD_COMMANDS: 28 | if arguments[1] == "--issue" or arguments[1] == "-i": 29 | return len(arguments) == 3 and arguments[2].isdigit() 30 | else: 31 | return len(arguments) == 2 32 | else: 33 | return False 34 | 35 | 36 | async def get_xkcd(message): 37 | """ 38 | Displays either a random XKCD comic, a specific issue, or the '!xkcd' help panel. 39 | Assumes that a valid command is contained within msg (checked by is_valid_xkcd_command(String)). 40 | """ 41 | msg = message.content.lower().split() 42 | cmd = msg[1] 43 | comic = None 44 | 45 | if cmd == "--help" or cmd == "-h": 46 | await display_help(message) 47 | return 48 | 49 | if cmd == "--random" or cmd == "-r": 50 | comic = get_random() 51 | elif cmd == "--issue" or cmd == "-i": 52 | comic = get_issue(msg[2]) 53 | elif cmd == "--curent" or cmd == "-c": 54 | comic = get_current() 55 | 56 | if comic: 57 | await display(comic, message) 58 | else: 59 | await message.channel.send( 60 | "Sorry, I can't find a comic right now. Please try again later." 61 | ) 62 | 63 | 64 | async def display(metadata, msg): 65 | if metadata: 66 | embed = discord.Embed( 67 | title="#" + str(metadata["num"]) + " - " + metadata["title"], 68 | description=metadata["alt"], 69 | ) 70 | embed.set_image(url=metadata["img"]) 71 | await msg.channel.send(embed=embed) 72 | 73 | 74 | async def display_help(msg): 75 | embed = discord.Embed(title="'!xkcd' Command Help") 76 | embed.add_field( 77 | name="--help (-h)", value="Displays help for the '!xkcd' command.", inline=False 78 | ) 79 | embed.add_field( 80 | name="--random (-r)", value="Displays a random XKCD issue.", inline=False 81 | ) 82 | embed.add_field( 83 | name="--issue (-i) #", value="Displays a specific XKCD issue #.", inline=False 84 | ) 85 | embed.add_field( 86 | name="--current (-c)", value="Displays the current XKCD issue.", inline=False 87 | ) 88 | await msg.channel.send(embed=embed) 89 | 90 | 91 | def get_issue(num): 92 | url = HOST + str(num) + "/" 93 | return get_json(url) 94 | 95 | 96 | def get_current(): 97 | return get_json(HOST) 98 | 99 | 100 | def get_json(url): 101 | try: 102 | return requests.get(url + "info.0.json").json() 103 | except json.JSONDecodeError: 104 | return None 105 | 106 | 107 | def get_random(): 108 | """ 109 | Retrieves the current issue of XKCD, chooses an issue 1 - current issue #, and returns a json object. 110 | Returns null if an requests error occurs. 111 | """ 112 | return get_issue(random.randint(1, int(get_current()["num"]))) 113 | -------------------------------------------------------------------------------- /apps/ldap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/ldap/__init__.py -------------------------------------------------------------------------------- /apps/ldap/export_ldap.py: -------------------------------------------------------------------------------- 1 | """ 2 | export_ldap.py 3 | 4 | Use this script to export the info from the LDAP server. 5 | 6 | Move the outputs of the script to the fixtures directory to run them with the ldap test suite. 7 | 8 | https://ldap3.readthedocs.io/mocking.html#a-complete-example 9 | """ 10 | import json 11 | 12 | from ldap3 import ALL, ALL_ATTRIBUTES, MOCK_SYNC, Connection, Server 13 | 14 | INFO_OUTPUT = "csua_ldap_info.json" 15 | SCHEMA_OUTPUT = "csua_ldap_schema.json" 16 | ENTRIES_OUTPUT = "csua_ldap_entries.json" 17 | 18 | if __name__ == "__main__": 19 | REAL_SERVER = "ldaps://ldap.csua.berkeley.edu" 20 | 21 | # Retrieve server info and schema from a real server 22 | server = Server(REAL_SERVER, get_info=ALL) 23 | connection = Connection(server, auto_bind=True) 24 | 25 | # Store server info and schema to json files 26 | server.info.to_file(INFO_OUTPUT) 27 | server.schema.to_file(SCHEMA_OUTPUT) 28 | 29 | # Read entries from a portion of the DIT from real server and store them in a json file 30 | if connection.search( 31 | "dc=csua,dc=berkeley,dc=edu", "(objectclass=*)", attributes=ALL_ATTRIBUTES 32 | ): 33 | raw_entries = connection.response_to_json(raw=True) 34 | entries = json.loads(raw_entries) 35 | filtered_entries = { 36 | "entries": [ 37 | i 38 | for i in entries["entries"] 39 | if "posixAccount" not in i["attributes"]["objectClass"] 40 | # don't add the accounts 41 | ] 42 | } 43 | with open(ENTRIES_OUTPUT, "w") as f: 44 | json.dump(filtered_entries, f, indent=2) 45 | else: 46 | raise RuntimeError("ldap search failed!") 47 | 48 | # Close the connection to the real server 49 | connection.unbind() 50 | 51 | # Create a fake server from the info and schema json files 52 | fake_server = Server.from_definition("csua_mock", INFO_OUTPUT, SCHEMA_OUTPUT) 53 | 54 | # Create a MockSyncStrategy connection to the fake server 55 | fake_connection = Connection(fake_server, client_strategy=MOCK_SYNC) 56 | 57 | # Populate the DIT of the fake server 58 | fake_connection.strategy.entries_from_json(ENTRIES_OUTPUT) 59 | 60 | # Add a fake user for Simple binding 61 | fake_connection.strategy.add_entry( 62 | "cn=django_test_user,ou=People,dc=csua,dc=berkeley,dc=edu", 63 | {"uid": "django_test_user", "userPassword": "P4SSW0RD!"}, 64 | ) 65 | 66 | # Bind to the fake server 67 | fake_connection.bind() 68 | -------------------------------------------------------------------------------- /apps/ldap/shell_tests/input.py: -------------------------------------------------------------------------------- 1 | """ 2 | Run at project root: 3 | python manage.py shell < apps/ldap/shell_tests/input.py > apps/ldap/shell_tests/output 4 | """ 5 | from datetime import datetime, timezone 6 | 7 | from apps.ldap import utils 8 | from apps.ldap.utils import get_user_creation_time 9 | 10 | dt_now = datetime.now(timezone.utc) 11 | print("Current time:", dt_now) 12 | print("Current time LDAP:", utils.datetime_to_ldap(dt_now)) 13 | dt = datetime(2021, 2, 13, 15, 8, 37) 14 | lt = utils.datetime_to_ldap(dt) 15 | print("date_time_to_ldap is correct?", lt == "20210213150837Z") 16 | print() 17 | 18 | mem = utils.get_members_older_than() 19 | print(len(mem), "members older than 1460 days") 20 | mem = utils.get_members_in_age_range() 21 | print(len(mem), "members younger than 180 days") 22 | print() 23 | 24 | members_0_5 = utils.get_members_in_age_range(0, 5) 25 | print(members_0_5) 26 | for m in members_0_5: 27 | print(dt_now - utils.str_to_datetime(get_user_creation_time(m))) 28 | print() 29 | 30 | members_0_10 = utils.get_members_in_age_range(0, 10) 31 | print(members_0_10) 32 | for m in members_0_10: 33 | print(dt_now - utils.str_to_datetime(get_user_creation_time(m))) 34 | print() 35 | 36 | members_5_15 = utils.get_members_in_age_range(5, 15) 37 | print(members_5_15) 38 | for m in members_5_15: 39 | print(dt_now - utils.str_to_datetime(get_user_creation_time(m))) 40 | print() 41 | -------------------------------------------------------------------------------- /apps/ldap/shell_tests/output: -------------------------------------------------------------------------------- 1 | Current time: 2021-02-15 22:26:19.187398+00:00 2 | Current time LDAP: 20210215222619Z 3 | date_time_to_ldap is correct? True 4 | 5 | 4658 members older than 1460 days 6 | 308 members younger than 180 days 7 | 8 | ['rgoldman', 'kreehal', 'ericche', 'dachung'] 9 | 3 days, 11:13:49.187398 10 | 2 days, 20:52:33.187398 11 | 2 days, 13:28:44.187398 12 | 1 day, 18:34:19.187398 13 | 14 | ['smunukutla', 'weipang', 'kching77', 'canjo', 'nayaslee', 'rgoldman', 'kreehal', 'ericche', 'dachung'] 15 | 6 days, 20:08:01.187398 16 | 6 days, 16:32:55.187398 17 | 6 days, 15:13:55.187398 18 | 6 days, 0:06:43.187398 19 | 5 days, 22:35:01.187398 20 | 3 days, 11:13:49.187398 21 | 2 days, 20:52:33.187398 22 | 2 days, 13:28:44.187398 23 | 1 day, 18:34:19.187398 24 | 25 | ['Andrei_Tan', 'catherinezhxng', 'Heezy', 'khoidng', 'Kl12', 'matthewrowland', 'khushiv3', 'klaire', 'sanjeevt', 'mvora', 'anuravohra', 'AryanJain', 'gautamanand', 'sebitommy123', 'jjaison13', 'agoyal', 'smunukutla', 'weipang', 'kching77', 'canjo', 'nayaslee'] 26 | 14 days, 20:30:10.187398 27 | 14 days, 17:03:28.187398 28 | 14 days, 15:37:45.187398 29 | 14 days, 15:28:41.187398 30 | 13 days, 23:32:19.187398 31 | 13 days, 20:35:52.187398 32 | 13 days, 18:43:42.187398 33 | 13 days, 13:57:43.187398 34 | 13 days, 13:41:18.187398 35 | 12 days, 21:08:46.187398 36 | 12 days, 20:47:58.187398 37 | 12 days, 15:13:26.187398 38 | 12 days, 12:41:54.187398 39 | 12 days, 2:04:45.187398 40 | 11 days, 20:41:10.187398 41 | 10 days, 14:14:55.187398 42 | 6 days, 20:08:01.187398 43 | 6 days, 16:32:55.187398 44 | 6 days, 15:13:55.187398 45 | 6 days, 0:06:43.187398 46 | 5 days, 22:35:01.187398 47 | 48 | -------------------------------------------------------------------------------- /apps/ldap/templates/ldap_admin.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load staticfiles %} 3 | {% block title%}LDAP Admin{% endblock %} 4 | {% block content %} 5 | {% block header %} 6 | 15 | {% endblock %} 16 | {% block messages %} 17 | {% if messages %} 18 |
19 |
20 |
    21 | {% for message in messages %} 22 |
  • {{ message.level_tag|upper }} {{ message }}
  • 23 | {% endfor %} 24 |
25 |
26 |
27 | {% endif %} 28 | {% endblock %} 29 | {% block page_content %} 30 |
31 |
32 |
33 |
34 |

Users

35 |
36 | 37 | 38 |
39 |

Group Role Validator

40 |
41 |
42 |

Relevant Groups

43 |
    44 | {% for group in relevant_groups %} 45 |
  • 46 | {{ group }} 47 |
  • 48 | {% endfor %} 49 |
50 |
51 |
52 |

All Groups

53 | 62 |
63 |
64 |
65 |
66 | {% endblock %} 67 | {% endblock %} 68 | {# vim: set et sw=2: #} 69 | -------------------------------------------------------------------------------- /apps/ldap/templates/ldap_admin_group.html: -------------------------------------------------------------------------------- 1 | {% extends "ldap_admin.html" %} 2 | {% block page_content %} 3 |
4 |
5 |
6 |
7 |

{{ groupname }}

8 |
    9 | {% for member in members %} 10 |
  • {{ member }}
  • 11 | {% endfor %} 12 |
13 |
14 | 15 | {% csrf_token %} 16 | 17 |
18 |
19 |
20 |

Remove users from {{ groupname }}

21 |
    22 |
    23 | 24 | {% csrf_token %} 25 | {% for member in members|dictsort:0 %} 26 |
  • 27 | 28 | 29 |
  • 30 | {% endfor %} 31 |
    32 | 33 |
    34 |
35 |
36 |
37 |

Add users to {{ groupname }}

38 |
39 | {% csrf_token %} 40 | {{ form.as_p }} 41 | 42 |
43 |
44 |
45 |
46 |
47 | {% endblock %} 48 | {# vim: set et sw=2: #} 49 | -------------------------------------------------------------------------------- /apps/ldap/templates/ldap_admin_group_checker.html: -------------------------------------------------------------------------------- 1 | {% extends "ldap_admin.html" %} 2 | {% block content %} 3 |
4 |
5 |
6 |
7 |

LDAP {{ groupname }}

8 |
    9 | {% for member in ldap %} 10 |
  • {{ member }}
  • 11 | {% endfor %} 12 |
13 |

LDAP missing {{ groupname }}

14 |
    15 | {% for member in ldap_missing %} 16 |
  • {{ member }}
  • 17 | {% endfor %} 18 |
19 |
20 |
21 |

Django {{ groupname }}

22 |
    23 | {% for member in django %} 24 |
  • {{ member }}
  • 25 | {% endfor %} 26 |
27 |

Django missing {{ groupname }}

28 |
    29 | {% for member in django_missing %} 30 |
  • {{ member }}
  • 31 | {% endfor %} 32 |
33 |
34 |
35 |
36 |
37 | {% endblock %} 38 | {# vim: set et sw=2: #} 39 | -------------------------------------------------------------------------------- /apps/ldap/templates/ldap_admin_user.html: -------------------------------------------------------------------------------- 1 | {% extends "ldap_admin.html" %} 2 | {% block content %} 3 |
4 |
5 |

WORK IN PROGRESS

6 |

{{ username }}

7 |
8 |
9 |
10 |
11 | {{ gecos }}
12 | {{ creation_time }} 13 |
14 |
15 |
16 | {% csrf_token %} 17 | {{ form }} 18 | 19 |
20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | {# vim: set et sw=2: #} 26 | -------------------------------------------------------------------------------- /apps/ldap/templates/ldap_admin_validate.html: -------------------------------------------------------------------------------- 1 | {% extends "ldap_admin.html" %} 2 | {% block page_content %} 3 |
4 |
5 |

LDAP + SQL Validation

6 | 7 | 8 | 11 | 14 | 17 | 20 | 21 | {% for role, values in roles.items %} 22 | 23 | 24 | {% with common=values.0 %} 25 | 34 | {% endwith %} 35 | {% with sql_only=values.1 %} 36 | 43 | {% endwith %} 44 | {% with ldap_only=values.2 %} 45 | 52 | {% endwith %} 53 | 54 | {% endfor %} 55 |
9 | Role 10 | 12 | Common 13 | 15 | SQL Only 16 | 18 | LDAP Only 19 |
{{ role }} 26 |
    27 | {% for username in common %} 28 |
  • {{ username }}
  • 29 | {% empty %} 30 | WARNING! 31 | {% endfor %} 32 |
33 |
37 |
    38 | {% for username in sql_only %} 39 |
  • {{ username }}
  • 40 | {% endfor %} 41 |
42 |
46 |
    47 | {% for username in ldap_only %} 48 |
  • {{ username }}
  • 49 | {% endfor %} 50 |
51 |
56 |
57 |
58 | {% endblock %} 59 | {# vim: set et sw=2: #} 60 | -------------------------------------------------------------------------------- /apps/ldap/test_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | from unittest.mock import patch 4 | 5 | import ldap3 6 | from django.conf import settings 7 | from django.test import TestCase 8 | 9 | from .utils import NEWUSER_DN 10 | 11 | TEST_NEWUSER_PW = "ilovepnunez" 12 | 13 | mock_ldap_server = ldap3.Server.from_definition( 14 | "", 15 | os.path.join(settings.BASE_DIR, "fixtures", "csua_ldap_info.json"), 16 | os.path.join(settings.BASE_DIR, "fixtures", "csua_ldap_schema.json"), 17 | ) 18 | 19 | 20 | class LDAPTestCase(TestCase): 21 | def setUp(self): 22 | self.patchers = [ 23 | patch("apps.ldap.utils.LDAP_SERVER", mock_ldap_server), 24 | patch("apps.ldap.utils.LDAP_CLIENT_STRATEGY", ldap3.MOCK_SYNC), 25 | patch("apps.ldap.utils.NEWUSER_PW", TEST_NEWUSER_PW), 26 | ] 27 | for p in self.patchers: 28 | p.start() 29 | with ldap3.Connection(mock_ldap_server, client_strategy=ldap3.MOCK_SYNC) as c: 30 | c.strategy.entries_from_json( 31 | os.path.join(settings.BASE_DIR, "fixtures", "csua_ldap_entries.json") 32 | ) 33 | c.strategy.add_entry( 34 | "uid=cnunez,ou=People,dc=csua,dc=berkeley,dc=edu", 35 | { 36 | "uid": "cnunez", 37 | "cn": "cnunez", 38 | "gecos": "C Nunez,cnunez@berkeley.edu", 39 | "uidNumber": 420, 40 | "userPassword": "pp", 41 | "objectClass": ["posixAccount"], 42 | }, 43 | ) 44 | c.strategy.add_entry( 45 | "uid=test_user,ou=People,dc=csua,dc=berkeley,dc=edu", 46 | { 47 | "uid": "test_user", 48 | "cn": "test_user", 49 | "uidNumber": 31337, 50 | "userPassword": "test_password", 51 | "objectClass": ["posixAccount"], 52 | }, 53 | ) 54 | c.strategy.add_entry( 55 | NEWUSER_DN, 56 | { 57 | "uid": "newuser", 58 | "userPassword": TEST_NEWUSER_PW, 59 | "objectClass": ["posixAccount"], 60 | }, 61 | ) 62 | 63 | def tearDown(self): 64 | for p in self.patchers: 65 | p.stop() 66 | -------------------------------------------------------------------------------- /apps/ldap/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | I wasn't able to find documentation on mocking LDAP servers that had good exaples, so I found 3 | https://github.com/StackFocus/PostMaster/blob/master/tests/ad/test_ad_class.py 4 | Which I based some of this code off of 5 | --robertquitt 6 | """ 7 | import os 8 | import unittest 9 | from datetime import datetime 10 | 11 | import ldap3 12 | from django.conf import settings 13 | from django.test import TestCase 14 | 15 | import apps.ldap.utils as utils 16 | from apps.ldap.test_helpers import LDAPTestCase 17 | 18 | from .utils import NEWUSER_DN 19 | 20 | 21 | class LdapBindingsTest(LDAPTestCase): 22 | """ 23 | Tests the LDAP code by mocking the CSUA LDAP server. 24 | """ 25 | 26 | def test_auth(self): 27 | result = utils.authenticate("test_user", "test_password") 28 | self.assertTrue(result) 29 | result = utils.authenticate("test_user", "wrong_password") 30 | self.assertFalse(result) 31 | 32 | def test_is_officer(self): 33 | result = utils.is_officer("robertq") 34 | # hopefully I'll be an officer forever :3 35 | self.assertTrue(result) 36 | 37 | result = utils.is_officer("dangengdg") 38 | self.assertFalse(result) 39 | 40 | def test_create_new_user_and_delete(self): 41 | max_uid = utils.get_max_uid() 42 | self.assertEquals(max_uid, 31337) 43 | 44 | success, uid_num = utils.create_new_user( 45 | "pnunez1", 46 | "Phillip E. Nunez", 47 | "pnunez1@berkeley.edu", 48 | 3116969, 49 | "il0vedangengdg!", 50 | ) 51 | self.assertTrue(success) 52 | self.assertEquals(uid_num, 31338) 53 | 54 | self.assertTrue(utils.user_exists("pnunez1")) 55 | success = utils.delete_user("pnunez1") 56 | self.assertTrue(success) 57 | self.assertFalse(utils.user_exists("pnunez1")) 58 | 59 | def test_datetime_to_ldap(self): 60 | dt = datetime(2021, 2, 13, 15, 8, 37) 61 | lt = utils.datetime_to_ldap(dt) 62 | self.assertEquals(lt, "20210213150837Z") 63 | 64 | # TODO: finish this 65 | # def test_password(self): 66 | # test_password = "dangengdg is my Friend" 67 | # ssha_pw = ldap_bindings.make_password(test_password) 68 | # self.assertTrue(ssha_pw.startswith("{SSHA}")) 69 | # digest_salt_b64 = ssha_pw[6:] 70 | 71 | # digest_salt = digest_salt_b64.decode('base64') 72 | # digest = digest_salt[:20] 73 | # salt = digest_salt[20:] 74 | 75 | # sha = hashlib.sha1(test_password) 76 | # sha.update(salt) 77 | 78 | # self.assertEquals(digest, sha.digest()) 79 | -------------------------------------------------------------------------------- /apps/ldap/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.static import static 2 | from django.contrib import admin 3 | from django.urls import include, path 4 | 5 | from . import views 6 | from .admin_views import admin, admin_group, admin_user, admin_validate 7 | 8 | urlpatterns = [ 9 | path("user//", views.user), 10 | path("user//groups/", views.user_groups), 11 | path("admin/", admin, name="ldap_admin"), 12 | path("admin/validate_groups/", admin_validate, name="ldap_admin_validate_groups"), 13 | path("admin/group//", admin_group, name="ldap_admin_group"), 14 | path("admin/user//", admin_user, name="ldap_admin_user"), 15 | ] 16 | -------------------------------------------------------------------------------- /apps/ldap/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from django.shortcuts import render 3 | from ldap3.utils.conv import escape_filter_chars 4 | 5 | from .utils import get_group_members, get_root, get_user_gecos, get_user_groups 6 | 7 | 8 | def group(request, groupname=None): 9 | groupname = escape_filter_chars(groupname) 10 | resp = "\n".join(get_group_members(groupname)) 11 | return HttpResponse(resp, content_type="text/plain") 12 | 13 | 14 | def user(request, username=None): 15 | username = escape_filter_chars(username) 16 | resp = get_user_gecos(username) 17 | return HttpResponse(resp, content_type="text/plain") 18 | 19 | 20 | def user_groups(request, username=None): 21 | username = escape_filter_chars(username) 22 | resp = "\n".join(get_user_groups(username)) 23 | return HttpResponse(resp, content_type="text/plain") 24 | -------------------------------------------------------------------------------- /apps/main_page/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/main_page/__init__.py -------------------------------------------------------------------------------- /apps/main_page/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/main_page/templatetags/__init__.py -------------------------------------------------------------------------------- /apps/main_page/templatetags/staticthumbnail.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.core.files.storage import FileSystemStorage 4 | from sorl.thumbnail.templatetags.thumbnail import ThumbnailNode 5 | 6 | register = template.Library() 7 | 8 | 9 | class StaticThumbnailStorage(FileSystemStorage): 10 | def __init__(self, *args, **kw): 11 | super(StaticThumbnailStorage, self).__init__( 12 | *args, location=settings.STATIC_ROOT, base_url=settings.STATIC_URL, **kw 13 | ) 14 | 15 | 16 | storage = StaticThumbnailStorage() 17 | 18 | 19 | class StaticThumbnailNode(ThumbnailNode): 20 | def _get_thumbnail(self, file_, geometry, **options): 21 | options["storage"] = storage 22 | return super(StaticThumbnailNode, self)._get_thumbnail( 23 | file_, geometry, **options 24 | ) 25 | 26 | 27 | @register.tag 28 | def static_thumbnail(parser, token): 29 | return StaticThumbnailNode(parser, token) 30 | -------------------------------------------------------------------------------- /apps/main_page/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import Client, TestCase 9 | 10 | 11 | class TestPages(TestCase): 12 | def setUp(self): 13 | self.client = Client() 14 | 15 | def test_index(self): 16 | response = self.client.get("/") 17 | self.assertEqual(response.status_code, 200) 18 | -------------------------------------------------------------------------------- /apps/main_page/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib.auth.views import LoginView, LogoutView 4 | from django.urls import include, path, re_path 5 | from django.views.generic.base import TemplateView 6 | 7 | from . import views 8 | 9 | urlpatterns = [ 10 | path("", TemplateView.as_view(template_name="index.html"), name="index"), 11 | path( 12 | "constitution/", 13 | TemplateView.as_view(template_name="constitution.html"), 14 | name="constitution", 15 | ), 16 | path( 17 | "tech/", 18 | TemplateView.as_view(template_name="tech.html"), 19 | name="tech", 20 | ), 21 | path("join/", TemplateView.as_view(template_name="join.html"), name="join"), 22 | path("alumni/", TemplateView.as_view(template_name="alumni.html"), name="alumni"), 23 | path( 24 | "login/", 25 | LoginView.as_view(template_name="registration/login.html"), 26 | name="login", 27 | ), 28 | path( 29 | "contact/", TemplateView.as_view(template_name="contact.html"), name="contact" 30 | ), 31 | path("logout/", LogoutView.as_view(), name="logout"), 32 | path("profile/", views.profile, name="my_profile"), 33 | path("profile//", views.profile, name="profile"), 34 | path("404/", TemplateView.as_view(template_name="404.html")), 35 | path("minutes/", TemplateView.as_view(template_name="meeting_minutes.html")), 36 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 37 | -------------------------------------------------------------------------------- /apps/main_page/views.py: -------------------------------------------------------------------------------- 1 | from django.http import Http404, HttpResponse 2 | from django.shortcuts import render 3 | from django.template import TemplateDoesNotExist 4 | from django.views.generic.base import TemplateView 5 | from ldap3 import Connection 6 | from ldap3.utils.conv import escape_filter_chars 7 | 8 | from apps.ldap.utils import get_user_groups, get_user_realname 9 | 10 | 11 | def profile(request, username=None): 12 | if not username: 13 | if request.user.is_authenticated: 14 | username = request.user.username 15 | else: 16 | raise Http404("No such user!") 17 | groups = get_user_groups(username) 18 | realname = get_user_realname(username) 19 | return render( 20 | request, 21 | "profile.html", 22 | {"username": username, "groups": groups, "realname": realname}, 23 | ) 24 | -------------------------------------------------------------------------------- /apps/newuser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/newuser/__init__.py -------------------------------------------------------------------------------- /apps/newuser/addMember.py: -------------------------------------------------------------------------------- 1 | #! /usr/local/bin/python3 2 | 3 | # See /root/root-work/auto-list-update for group name query script and README 4 | 5 | import json 6 | import os 7 | import sys 8 | 9 | from google.oauth2 import service_account 10 | from googleapiclient.discovery import build 11 | 12 | # Scopes required for accessing Google Groups (Cloud Identity API) 13 | SCOPES = ["https://www.googleapis.com/auth/cloud-identity.groups"] 14 | 15 | # Path to the Service Account JSON key file 16 | SERVICE_ACCOUNT_FILE = "/webserver/csua-backend/csua-rso-mail-manager-a2ae498a619c.json" 17 | 18 | # The google group's ID, found by using list users with the google group email 19 | # this is the ID for: csua-members@lists.berkeley.edu 20 | GROUP_NAME = "groups/01tuee740igmbim" 21 | 22 | 23 | def authenticate(): 24 | # Authenticate using a service account 25 | creds = service_account.Credentials.from_service_account_file( 26 | SERVICE_ACCOUNT_FILE, scopes=SCOPES 27 | ) 28 | return creds 29 | 30 | 31 | def create_service(): 32 | creds = authenticate() 33 | service = build("cloudidentity", "v1", credentials=creds) 34 | return service 35 | 36 | 37 | def create_google_group_membership(service, member_key): 38 | try: 39 | membership = { 40 | "preferredMemberKey": {"id": member_key}, 41 | "roles": [{"name": "MEMBER"}], 42 | } 43 | response = ( 44 | service.groups() 45 | .memberships() 46 | .create(parent=GROUP_NAME, body=membership) 47 | .execute() 48 | ) 49 | print(response) 50 | except Exception as e: 51 | print(e) 52 | 53 | 54 | def add_member(email): 55 | # Add an email to the Google Group 56 | service = create_service() 57 | create_google_group_membership(service, email) 58 | 59 | 60 | def main(): 61 | add_member(sys.argv[1]) 62 | 63 | 64 | if __name__ == "__main__": 65 | sys.exit(main()) 66 | -------------------------------------------------------------------------------- /apps/newuser/config_newuser: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #!/bin/rbash 3 | #arguments: config_newuser username email uid enroll_jobs 4 | if [ $# -ne 4 ]; then echo "usage: $0 username email uid enroll_jobs"; exit 1; fi 5 | mkdir -m 700 /nfs/homes/$1 6 | echo $2 > /nfs/homes/$1/.forward 7 | chown -R $3:1000 /nfs/homes/$1 8 | # TODO: add back mailman enrollment 9 | #echo $2 | add_members -r - Csua-newmembers 10 | #if ($4); then echo $2 | add_members -r - Jobs; fi 11 | 12 | # Enroll user in csua-members google group (temp solution until mailman) 13 | /webserver/csua-backend/apps/newuser/addMember.py $2 14 | 15 | #this is for creating the public_html folder and link 16 | mkdir -m 755 /nfs/public_html/$1 17 | chown -R $3:1000 /nfs/public_html/$1 18 | ln -s /csua/public_html/$1 /nfs/homes/$1/public_html 19 | -------------------------------------------------------------------------------- /apps/newuser/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.exceptions import ValidationError 3 | 4 | from apps.ldap.utils import user_exists 5 | 6 | from .utils import valid_password 7 | 8 | usernameWhitelist = set(".-_") 9 | 10 | 11 | def validate_username_chars(value): 12 | if not all(c.isalnum() or c in usernameWhitelist for c in value): 13 | raise ValidationError( 14 | "Username must consist of alphanumeric characters and ., -, or _" 15 | ) 16 | 17 | 18 | def validate_username_not_in_use(value): 19 | if user_exists(value): 20 | raise ValidationError(f"Username {value} is taken") 21 | 22 | 23 | class RemoteEmailRequestForm(forms.Form): 24 | email = forms.EmailField(label="Email") 25 | 26 | 27 | class NewUserForm(forms.Form): 28 | full_name = forms.CharField(label="Full Name") 29 | student_id = forms.IntegerField( 30 | label="Student ID", min_value=100000, max_value=10000000000 31 | ) 32 | email = forms.EmailField() 33 | username = forms.CharField( 34 | validators=[validate_username_chars, validate_username_not_in_use] 35 | ) 36 | password = forms.CharField(widget=forms.PasswordInput()) 37 | # enroll_jobs = forms.BooleanField(required=False, label="Jobs@ List Opt-in") 38 | agree_rules = forms.BooleanField(required=True) 39 | 40 | def clean(self): 41 | form_data = super().clean() 42 | password = form_data.get("password") 43 | if not valid_password(password): 44 | raise ValidationError("Password must meet requirements") 45 | return form_data 46 | 47 | 48 | class NewUserFormOfficerVerified(NewUserForm): 49 | officer_username = forms.CharField(label="Officer Username") 50 | officer_password = forms.CharField( 51 | widget=forms.PasswordInput(), label="Officer Password" 52 | ) 53 | -------------------------------------------------------------------------------- /apps/newuser/tests.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import unittest 4 | from unittest.mock import Mock, patch 5 | 6 | from django.core import mail 7 | from django.test import TestCase 8 | from django.urls import reverse 9 | 10 | from apps.ldap.test_helpers import LDAPTestCase 11 | from apps.ldap.utils import email_exists 12 | from apps.newuser.views import newuser_script 13 | 14 | from .tokens import newuser_token_generator 15 | 16 | 17 | class NewUserTest(LDAPTestCase): 18 | """ 19 | Tests the LDAP code by mocking the CSUA LDAP server. 20 | 21 | """ 22 | 23 | # TODO: include tests for failure modes (newuser bind fail, config_newuser 24 | # fails) 25 | @patch("subprocess.run") 26 | def test_remote_newuser_flow(self, subprocess_run): 27 | logging.disable(logging.CRITICAL) 28 | 29 | url = "/newuser/remote/" 30 | email = "pnunez2@berkeley.edu" 31 | resp = self.client.get(url) 32 | resp = self.client.post(url, {"email": email}) 33 | self.assertContains(resp, "Email sent") 34 | self.assertEqual(len(mail.outbox), 1) 35 | self.assertEqual(mail.outbox[0].to, [email]) 36 | token_url = reverse( 37 | "newuser-remote", 38 | kwargs={"email": email, "token": newuser_token_generator.make_token(email)}, 39 | ) 40 | full_url = "https://www.csua.berkeley.edu" + token_url 41 | self.assertIn(full_url, mail.outbox[0].body) 42 | 43 | subprocess_run.return_value.returncode = 0 44 | self.assertFalse(email_exists(email)) 45 | resp = self.client.get(token_url) 46 | resp = self.client.post( 47 | token_url, 48 | { 49 | "full_name": "Phillip E. Nunez II", 50 | "student_id": 3114201612, 51 | "email": email, 52 | "username": "pnunez2", 53 | "password": "okPASSWORD1!", 54 | "enroll_jobs": False, 55 | "agree_rules": True, 56 | }, 57 | ) 58 | self.assertEqual(len(subprocess_run.call_args_list), 1) 59 | self.assertNotContains(resp, "failed") 60 | args = subprocess_run.call_args_list[0][0][0] 61 | self.assertIs(type(args), list) 62 | for arg in args: 63 | self.assertIs(type(arg), str) 64 | self.assertTrue(email_exists(email)) 65 | 66 | logging.disable(logging.NOTSET) 67 | -------------------------------------------------------------------------------- /apps/newuser/tokens.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | from django.utils.crypto import salted_hmac 3 | from django.utils.http import int_to_base36 4 | 5 | from apps.ldap.utils import email_exists 6 | 7 | 8 | class NewUserTokenGenerator(PasswordResetTokenGenerator): 9 | def _make_hash_value(self, email, timestamp): 10 | return str(email) + str(email_exists(email)) + str(timestamp) 11 | 12 | def _make_token_with_timestamp(self, email, timestamp): 13 | ts_b36 = int_to_base36(timestamp) 14 | 15 | hash = salted_hmac( 16 | self.key_salt, self._make_hash_value(email, timestamp) 17 | ).hexdigest()[::2] 18 | return "%s-%s" % (ts_b36, hash) 19 | 20 | 21 | newuser_token_generator = NewUserTokenGenerator() 22 | -------------------------------------------------------------------------------- /apps/newuser/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.index, name="newuser"), 7 | path("remote/", views.request_remote_newuser, name="newuser-remote-request"), 8 | path("remote///", views.remote_newuser, name="newuser-remote"), 9 | ] 10 | -------------------------------------------------------------------------------- /apps/newuser/utils.py: -------------------------------------------------------------------------------- 1 | def valid_password(password): 2 | """ 3 | The password must be at least nine characters long. Also, it must include 4 | characters from two of: 5 | 6 | -alphabetical 7 | -numerical 8 | -punctuation/other 9 | """ 10 | punctuation = set("""!@#$%^&*()_+|~-=\\`{}[]:";'<>?,./""") 11 | alpha = False 12 | num = False 13 | punct = False 14 | 15 | if len(password) < 9: 16 | return False 17 | 18 | for character in password: 19 | if character.isalpha(): 20 | alpha = True 21 | if character.isdigit(): 22 | num = True 23 | if character in punctuation: 24 | punct = True 25 | return (alpha + num + punct) >= 2 26 | -------------------------------------------------------------------------------- /apps/outreach/templates/outreach/email_divider.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 32 | 33 | 34 |
15 | 23 | 24 | 25 | 28 | 29 | 30 |
26 | 27 |
31 |
35 | -------------------------------------------------------------------------------- /apps/outreach/templates/outreach/event_detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ forloop.counter }}. {{ event.name }} 4 | 5 | 6 |
7 | 8 | 9 | {{ event.date|date:"l, F jS" }}, {{ event.time }}
10 | {{ event.location }} 11 |
12 |
13 | Link:  17 | {{ event.link }} 18 | 19 |
20 | {{ event.description|escape|urlize|linebreaksbr }} 21 |
22 | -------------------------------------------------------------------------------- /apps/outreach/templates/outreach/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Outreach Dashboard{% endblock %} 3 | {% block content %} 4 | 20 | {% endblock %} 21 | {# vim: set et sw=4 ts=4: #} 22 | -------------------------------------------------------------------------------- /apps/outreach/templates/outreach/preview.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Outreach Email Preview{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 | 26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 | {% endblock %} 37 | {# vim: set et sw=4 ts=4: #} 38 | -------------------------------------------------------------------------------- /apps/outreach/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("mailme/", views.sendmail), 7 | path("preview/", views.preview), 8 | path("preview_iframe/", views.preview_iframe), 9 | path("", views.index), 10 | ] 11 | -------------------------------------------------------------------------------- /apps/outreach/views.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from django.core import mail 4 | from django.http import HttpResponse 5 | from django.shortcuts import render 6 | from django.template.loader import render_to_string 7 | from django.utils.html import strip_tags 8 | 9 | from apps.db_data.models import Event 10 | 11 | 12 | def get_html_email(): 13 | today = datetime.today() 14 | print(today) 15 | events = Event.objects.filter(date__gte=today).order_by("date") 16 | return render_to_string( 17 | "outreach/upcoming_events_email.html", 18 | { 19 | "events": events, 20 | "upper_left_text": "CSUA: Upcoming Events Newsletter", 21 | "header_image_url": "https://www.csua.berkeley.edu/static/images/pic.png", 22 | "small_title": "UC Berkeley Computer Science Undergraduate Association", 23 | "big_title": "UPCOMING EVENTS", 24 | "before_toc": "Announcements!", 25 | "preview_text": ", ".join(e.name for e in events), 26 | "title": "CSUA Upcoming Events", 27 | }, 28 | # Old email image: "header_image_url": "https://gallery.mailchimp.com/3e2d3e62274ea01781b01bd2d/images/01a7e34b-1b9a-4625-9259-2ef05decf823.png", 29 | ) 30 | 31 | 32 | def index(request): 33 | return render(request, "outreach/index.html") 34 | 35 | 36 | def preview(request): 37 | html_message = get_html_email() 38 | return render(request, "outreach/preview.html") 39 | 40 | 41 | def preview_iframe(request): 42 | return HttpResponse(get_html_email()) 43 | 44 | 45 | def sendmail(request): 46 | html_message = get_html_email() 47 | subject = "Test Email" 48 | plain_message = strip_tags(html_message) 49 | from_email = "Robert Quitt " 50 | to = "robertq@csua.berkeley.edu" 51 | mail.send_mail(subject, plain_message, from_email, [to], html_message=html_message) 52 | return HttpResponse("done") 53 | 54 | 55 | def gcal(request): 56 | pass 57 | 58 | 59 | """ 60 | credentials = ServiceAccountCredentials.from_json_keyfile_name( 61 | filename="", scopes=SCOPES 62 | ) 63 | """ 64 | -------------------------------------------------------------------------------- /apps/password_reset/README.md: -------------------------------------------------------------------------------- 1 | # CSUA Password Reset 2 | 3 | To reset passwords we use a token-based system. 4 | 5 | ## Process 6 | 7 | 1. User requests password reset (`RequestPasswordResetView`) 8 | 2. Token is created and sent to user's email (as specified in LDAP) 9 | 3. User follows email link with token 10 | 4. User resets password (`PasswordResetView`) 11 | 12 | ## Tokens 13 | 14 | Tokens have a few properties: 15 | 16 | - Expire after 3 days (default `settings.PASSWORD_RESET_TIMEOUT_DAYS`) 17 | - Are invalidated upon successful password change 18 | - Do not leak user's password 19 | 20 | They are generated from a timestamp and a salted HMAC of the timestamp using the hashed password from LDAP as the key. 21 | This means that once the hashed password is changed, the token is invalidated. 22 | 23 | ``` 24 | timestamp <- days since 1/1/2001 25 | hash_value <- LDAP hashed password + timestamp 26 | mac <- HMAC_SHA1(timestamp, hash_value) 27 | token <- timestamp + "-" + mac 28 | ``` 29 | 30 | See: https://github.com/django/django/blob/stable/2.2.x/django/contrib/auth/tokens.py 31 | -------------------------------------------------------------------------------- /apps/password_reset/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/password_reset/__init__.py -------------------------------------------------------------------------------- /apps/password_reset/tokens.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | from django.utils.crypto import salted_hmac 3 | from django.utils.http import int_to_base36 4 | 5 | from apps.ldap.utils import get_user_hashed_password 6 | 7 | 8 | class AccountActivationTokenGenerator(PasswordResetTokenGenerator): 9 | def _make_hash_value(self, user, timestamp): 10 | # This uses the old hashed password and timestamp as a hash value 11 | # so that 1. you manage the time and 2. when you change the password 12 | # it becomes invalidated 13 | 14 | return str(get_user_hashed_password(user)) + str(timestamp) 15 | 16 | def _make_token_with_timestamp(self, user, timestamp): 17 | ts_b36 = int_to_base36(timestamp) 18 | 19 | hash = salted_hmac( 20 | self.key_salt, self._make_hash_value(user, timestamp) 21 | ).hexdigest()[::2] 22 | return "%s-%s" % (ts_b36, hash) 23 | 24 | 25 | account_activation_token = AccountActivationTokenGenerator() 26 | -------------------------------------------------------------------------------- /apps/password_reset/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.tokens import PasswordResetTokenGenerator 2 | from django.urls import path 3 | 4 | from . import views 5 | 6 | app_name = "password_reset" 7 | 8 | urlpatterns = [ 9 | path("", views.RequestPasswordResetView, name="request-reset-password"), 10 | path("token/", views.RequestPasswordResetView, name="reset-password-token"), 11 | path( 12 | "reset-password-confirm//", 13 | views.PasswordResetView.as_view(), 14 | name="reset-password-confirm", 15 | ), 16 | ] 17 | -------------------------------------------------------------------------------- /apps/slackbot/README.md: -------------------------------------------------------------------------------- 1 | # Philbot documentation 2 | 3 | Welcome to Philbot! 4 | 5 | Philbot is a slack bot for CSUA. Here is the link to the app: 6 | 7 | 8 | -------------------------------------------------------------------------------- /apps/slackbot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/slackbot/__init__.py -------------------------------------------------------------------------------- /apps/slackbot/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /apps/slackbot/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PhilbotConfig(AppConfig): 5 | name = "philbot" 6 | -------------------------------------------------------------------------------- /apps/slackbot/client.py: -------------------------------------------------------------------------------- 1 | import re 2 | import shlex 3 | import socket 4 | import subprocess 5 | 6 | from decouple import config 7 | from django.conf import settings 8 | from slack_bolt import App 9 | 10 | username_regexp = re.compile(r"[A-Za-z0-9.\-_]+") 11 | 12 | IS_PROD = socket.gethostname() == "tap" 13 | 14 | SLACK_BOT_USER_TOKEN = config("SLACK_BOT_USER_TOKEN", default="") 15 | SLACK_SIGNING_SECRET = config("SLACK_SIGNING_SECRET", default="") 16 | 17 | 18 | def message_based(message, say): 19 | for reacc in ["b", "a", "five", "e-mail", "id"]: 20 | app.client.reactions_add( 21 | channel=message["channel"], timestamp=message["ts"], name=reacc 22 | ) 23 | 24 | 25 | def event_message(event, say): 26 | pass 27 | 28 | 29 | def command_export(ack, say, command): 30 | ack("gotcha") 31 | 32 | 33 | def command_help(ack, say, command): 34 | ack("Help, I'm trapped in a chatbot factory!", response_type="in_channel") 35 | 36 | 37 | def run_shell_command(command): 38 | if not IS_PROD: 39 | command = "ssh soda " + command 40 | return subprocess.check_output(command, shell=True).decode() 41 | 42 | 43 | def command_finger(ack, say, command): 44 | args = command.get("text", "") 45 | if len(args.split()) != 1: 46 | ack("Usage: /philfinger ") 47 | return 48 | elif not username_regexp.match(args): 49 | ack("Username is invalid :weaver:") 50 | return 51 | csua_user = args.strip() 52 | ack(f"P:hilfinger:ing {csua_user}...") 53 | slack_user_info = app.client.users_info(user=command["user_id"]) 54 | print(slack_user_info) 55 | if not slack_user_info["ok"]: 56 | slack_user_info = {"user": {"profile": {"display_name": "pnunez"}}} 57 | finger_output = run_shell_command(f"finger -m {csua_user} | head -n 2") 58 | slack_name = ( 59 | slack_user_info["user"]["profile"]["display_name"] 60 | or slack_user_info["user"]["name"] 61 | ) 62 | if finger_output: 63 | say( 64 | f"""{slack_name} p:hilfinger:ed {csua_user}@csua.berkeley.edu: 65 | ```{finger_output}```""", 66 | response_type="in_channel", 67 | ) 68 | else: 69 | say(f"{slack_name} tried to finger {csua_user} but failed!") 70 | 71 | 72 | def command_man(ack, say, command): 73 | command_text = command["text"] 74 | command_text_clean = shlex.quote(command_text) 75 | ack("Fetching man page...") 76 | man_proc = subprocess.Popen( 77 | "ssh soda man".split(" ") + [command_text_clean], 78 | shell=False, 79 | stdout=subprocess.PIPE, 80 | stderr=subprocess.PIPE, 81 | ) 82 | man_response = man_proc.stdout.read().decode() 83 | if man_response: 84 | text = "man {}\n{}".format(command_text, man_response) 85 | else: 86 | text = "Couldn't find man page. Got: {}.".format(man_proc.stderr.read()) 87 | say(text, response_type="in_channel") 88 | 89 | 90 | if SLACK_BOT_USER_TOKEN and SLACK_SIGNING_SECRET: 91 | app = App(token=SLACK_BOT_USER_TOKEN, signing_secret=SLACK_SIGNING_SECRET) 92 | app.message("based")(message_based) 93 | app.event("message")(event_message) 94 | app.command("/man")(command_man) 95 | app.command("/philfinger")(command_finger) 96 | app.command("/export")(command_export) 97 | app.command("/help")(command_help) 98 | else: 99 | app = None 100 | -------------------------------------------------------------------------------- /apps/slackbot/log.py: -------------------------------------------------------------------------------- 1 | from logging import Formatter, StreamHandler 2 | 3 | from .client import SLACK_BOT_USER_TOKEN, app 4 | 5 | CSUA_PHILBOT_TESTING_CHANNEL_ID = "CCU09PNGL" 6 | CSUA_WEBSITE_UPDATES_CHANNEL_ID = "CG49A3UF8" 7 | 8 | 9 | class SlackMessageHandler(StreamHandler): 10 | def __init__(self): 11 | super().__init__() 12 | 13 | def emit(self, record): 14 | text = self.format(record) 15 | app.client.chat_postMessage(channel=CSUA_WEBSITE_UPDATES_CHANNEL_ID, text=text) 16 | 17 | 18 | def formatter(): 19 | return Formatter("*{levelname}* {name}.{funcName}:{lineno} {message}", style="{") 20 | 21 | 22 | def enabled(): 23 | """Used by apps.csua_backend.settings.LOGGING""" 24 | 25 | def f(record): 26 | return SLACK_BOT_USER_TOKEN is not None 27 | 28 | return f 29 | -------------------------------------------------------------------------------- /apps/slackbot/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/slackbot/migrations/__init__.py -------------------------------------------------------------------------------- /apps/slackbot/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /apps/slackbot/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | 4 | class SlackCommandSanityTest(TestCase): 5 | def test_command_help(self): 6 | # Zero test coverage baybeeeee 7 | pass 8 | 9 | def test_command_finger(self): 10 | pass 11 | -------------------------------------------------------------------------------- /apps/slackbot/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [path("events/", views.events)] 6 | -------------------------------------------------------------------------------- /apps/slackbot/views.py: -------------------------------------------------------------------------------- 1 | from django.views.decorators.csrf import csrf_exempt 2 | from slack_bolt.adapter.django import SlackRequestHandler 3 | 4 | from .client import app 5 | 6 | if app is not None: 7 | handler = SlackRequestHandler(app) 8 | else: 9 | handler = None 10 | 11 | 12 | @csrf_exempt 13 | def events(request): 14 | if handler is not None: 15 | return handler.handle(request) 16 | -------------------------------------------------------------------------------- /apps/tracker/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/tracker/__init__.py -------------------------------------------------------------------------------- /apps/tracker/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Computer, User 4 | 5 | 6 | @admin.register(User) 7 | class UserAdmin(admin.ModelAdmin): 8 | readonly_fields = ["username", "last_ping"] 9 | list_display = ["username", "time_spent", "last_ping"] 10 | 11 | 12 | @admin.register(Computer) 13 | class ComputerAdmin(admin.ModelAdmin): 14 | readonly_fields = ["local_timestamp"] 15 | list_display = ["hostname", "user"] 16 | -------------------------------------------------------------------------------- /apps/tracker/client.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from hashlib import sha512 3 | from json import dumps 4 | from os import environ 5 | from platform import node 6 | from random import randint 7 | from sys import argv 8 | from time import sleep, time 9 | from urllib.request import URLError, urlopen 10 | 11 | try: 12 | from ctypes import windll 13 | except ImportError: 14 | pass 15 | 16 | DEBUG = True 17 | if DEBUG: 18 | CURL_STRING = "http://localhost:8000/computers/ping/{0}/{1}" 19 | else: 20 | CURL_STRING = "http://csua.berkeley.edu/computers/ping/{0}/{1}" 21 | DELTA = 5 22 | 23 | 24 | n = 24758167959654528007156374531915464081839760935532218683689708649238085888673119 25 | d = 23033243203809603228118865178853745404793524490245401081883537446288476045877277 26 | 27 | 28 | def isLogin(): 29 | # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684303(v=vs.85).aspx 30 | hDesktop = windll.User32.OpenDesktopW("default", 0, False, 0x0100) 31 | return windll.User32.SwitchDesktop(hDesktop) 32 | 33 | 34 | def signature(msg): 35 | return pow(int(sha512(msg.encode()).hexdigest(), 16) % n, d, n) 36 | 37 | 38 | def getEnv(): 39 | env = {} 40 | env["delta"] = DELTA 41 | env["username"] = environ.get("USERNAME") 42 | env["host"] = node() 43 | env["salt"] = randint(0, 21398571238905127987) 44 | env["timestamp"] = int(time() * 1000) 45 | return env 46 | 47 | 48 | def get_code_text(env): 49 | env_json = dumps(env) 50 | return base64.b64encode(env_json.encode("utf-8")).decode("utf-8") 51 | 52 | 53 | def get_request_url(env): 54 | codeText = get_code_text(env) 55 | return CURL_STRING.format(codeText, signature(codeText)) 56 | 57 | 58 | def ping(request_url): 59 | return urlopen(request_url).read().strip() 60 | 61 | 62 | if __name__ == "__main__": 63 | while True: 64 | if isLogin(): 65 | env = getEnv() 66 | request_url = get_request_url(env) 67 | try: 68 | print("Time Remaining: ", ping(request_url)) 69 | except URLError as e: 70 | print("Network Error: " + str(e)) 71 | sleep(5) 72 | -------------------------------------------------------------------------------- /apps/tracker/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-08-10 10:01 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Computer", 15 | fields=[ 16 | ( 17 | "hostname", 18 | models.CharField(max_length=15, primary_key=True, serialize=False), 19 | ), 20 | ("foreign_timestamp", models.IntegerField(default=0)), 21 | ("local_timestamp", models.IntegerField(default=0)), 22 | ], 23 | ), 24 | migrations.CreateModel( 25 | name="User", 26 | fields=[ 27 | ( 28 | "username", 29 | models.CharField(max_length=32, primary_key=True, serialize=False), 30 | ), 31 | ("last_ping", models.IntegerField(default=0)), 32 | ("time_spent", models.IntegerField(default=0)), 33 | ], 34 | ), 35 | migrations.AddField( 36 | model_name="computer", 37 | name="user", 38 | field=models.OneToOneField( 39 | on_delete=django.db.models.deletion.PROTECT, to="tracker.User" 40 | ), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /apps/tracker/migrations/0002_auto_20180810_0313.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-08-10 10:13 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [("tracker", "0001_initial")] 9 | 10 | operations = [ 11 | migrations.AlterField( 12 | model_name="computer", 13 | name="user", 14 | field=models.OneToOneField( 15 | null=True, 16 | on_delete=django.db.models.deletion.PROTECT, 17 | to="tracker.User", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /apps/tracker/migrations/0003_auto_20181026_2013.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2018-10-27 03:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [("tracker", "0002_auto_20180810_0313")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="computer", 12 | name="foreign_timestamp", 13 | field=models.DateTimeField(auto_now_add=True), 14 | ), 15 | migrations.AlterField( 16 | model_name="computer", 17 | name="local_timestamp", 18 | field=models.DateTimeField(auto_now_add=True), 19 | ), 20 | migrations.AlterField( 21 | model_name="user", 22 | name="last_ping", 23 | field=models.DateTimeField(auto_now_add=True), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /apps/tracker/migrations/0004_auto_20190204_2243.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.6 on 2019-02-05 06:43 2 | 3 | import django.db.models.deletion 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [("tracker", "0003_auto_20181026_2013")] 9 | 10 | operations = [ 11 | migrations.RemoveField(model_name="computer", name="foreign_timestamp"), 12 | migrations.AlterField( 13 | model_name="computer", 14 | name="local_timestamp", 15 | field=models.DateTimeField(auto_now=True), 16 | ), 17 | migrations.AlterField( 18 | model_name="computer", 19 | name="user", 20 | field=models.ForeignKey( 21 | null=True, 22 | on_delete=django.db.models.deletion.PROTECT, 23 | to="tracker.User", 24 | ), 25 | ), 26 | migrations.AlterField( 27 | model_name="user", 28 | name="last_ping", 29 | field=models.DateTimeField(auto_now=True), 30 | ), 31 | migrations.AlterField( 32 | model_name="user", 33 | name="username", 34 | field=models.CharField( 35 | editable=False, 36 | max_length=32, 37 | primary_key=True, 38 | serialize=False, 39 | unique=True, 40 | ), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /apps/tracker/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/apps/tracker/migrations/__init__.py -------------------------------------------------------------------------------- /apps/tracker/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | 5 | 6 | def seconds_to_time(seconds: int) -> str: 7 | sign = "" if seconds >= 0 else "-" 8 | mag = abs(seconds) 9 | m, s = divmod(mag, 60) 10 | h, m = divmod(m, 60) 11 | return "%s%d:%02d:%02d" % (sign, h, m, s) 12 | 13 | 14 | class User(models.Model): 15 | username = models.CharField( 16 | max_length=32, primary_key=True, unique=True, editable=False 17 | ) 18 | last_ping = models.DateTimeField(auto_now=True, editable=True) 19 | time_spent = models.IntegerField(default=0) 20 | 21 | @property 22 | def time(self): 23 | return seconds_to_time(self.time_spent) 24 | 25 | @property 26 | def realname(self): 27 | # TODO: make an LDAP query here and cache the result. 28 | return self.username 29 | 30 | 31 | class Computer(models.Model): 32 | hostname = models.CharField(max_length=15, primary_key=True) 33 | user = models.ForeignKey("User", on_delete=models.PROTECT, null=True) 34 | local_timestamp = models.DateTimeField(auto_now=True) 35 | 36 | @property 37 | def open(self): 38 | return self.user.time_spent >= 7200 39 | 40 | @property 41 | def time(self): 42 | return seconds_to_time(self.user.time_spent) 43 | -------------------------------------------------------------------------------- /apps/tracker/tests.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from django.http import HttpRequest 4 | from django.test import Client, TestCase 5 | 6 | import apps.tracker.client as client 7 | import apps.tracker.views as views 8 | 9 | 10 | class ViewsSanityTest(TestCase): 11 | def test_1_ping(self): 12 | env = { 13 | "delta": 5, 14 | "username": "pnunez", 15 | "host": "soda", 16 | "salt": 123456789, 17 | "timestamp": int(time.time() * 1000), 18 | } 19 | code_text = client.get_code_text(env) 20 | signature = str(client.signature(code_text)) 21 | 22 | c = Client() 23 | url = "/computers/ping/{0}/{1}".format(code_text, signature) 24 | response = c.get(url, follow=True) 25 | self.assertEqual(response.status_code, 200) 26 | 27 | def test_2_views(self): 28 | c = Client() 29 | 30 | response = c.get("/computers/json", follow=True) 31 | self.assertEquals(response.status_code, 200) 32 | 33 | def test_3_multiple_logins(self): 34 | env = { 35 | "delta": 5, 36 | "username": "pnunez", 37 | "host": "soda", 38 | "salt": 123456789, 39 | "timestamp": int(time.time() * 1000), 40 | } 41 | code_text = client.get_code_text(env) 42 | signature = str(client.signature(code_text)) 43 | c = Client() 44 | response = c.get( 45 | "/computers/ping/{0}/{1}".format(code_text, signature), follow=True 46 | ) 47 | 48 | env2 = { 49 | "delta": 5, 50 | "username": "pnunez", 51 | "host": "tap", 52 | "salt": 123456789, 53 | "timestamp": int(time.time() * 1000), 54 | } 55 | code_text = client.get_code_text(env) 56 | signature = str(client.signature(code_text)) 57 | c2 = Client() 58 | response = c2.get( 59 | "/computers/ping/{0}/{1}".format(code_text, signature), follow=True 60 | ) 61 | 62 | self.assertEqual(response.status_code, 200) 63 | -------------------------------------------------------------------------------- /apps/tracker/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | urlpatterns = [ 6 | path("", views.index), 7 | path("ping///", views.ping), 8 | path("json/", views.json), 9 | ] 10 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | repo_dir=$(dirname $0) 3 | 4 | python3 -m venv venv 5 | . venv/bin/activate 6 | pip install -U pip setuptools 7 | pip install poetry 8 | poetry install 9 | 10 | if [ ! -e "${repo_dir}/.env" ]; then 11 | cp "${repo_dir}/.env.dev" "${repo_dir}/.env" 12 | fi 13 | python3 manage.py migrate 14 | python3 manage.py createcachetable 15 | pre-commit install 16 | -------------------------------------------------------------------------------- /fixtures/csua_ldap_info.json: -------------------------------------------------------------------------------- 1 | { 2 | "raw": { 3 | "altServer": [], 4 | "configContext": [ 5 | "cn=config" 6 | ], 7 | "entryDN": [ 8 | "" 9 | ], 10 | "namingContexts": [ 11 | "dc=csua,dc=berkeley,dc=edu" 12 | ], 13 | "objectClass": [ 14 | "top", 15 | "OpenLDAProotDSE" 16 | ], 17 | "structuralObjectClass": [ 18 | "OpenLDAProotDSE" 19 | ], 20 | "subschemaSubentry": [ 21 | "cn=Subschema" 22 | ], 23 | "supportedCapabilities": [], 24 | "supportedControl": [ 25 | "2.16.840.1.113730.3.4.18", 26 | "2.16.840.1.113730.3.4.2", 27 | "1.3.6.1.4.1.4203.1.10.1", 28 | "1.3.6.1.1.22", 29 | "1.2.840.113556.1.4.319", 30 | "1.2.826.0.1.3344810.2.3", 31 | "1.3.6.1.1.13.2", 32 | "1.3.6.1.1.13.1", 33 | "1.3.6.1.1.12" 34 | ], 35 | "supportedExtension": [ 36 | "1.3.6.1.4.1.1466.20037", 37 | "1.3.6.1.4.1.4203.1.11.1", 38 | "1.3.6.1.4.1.4203.1.11.3", 39 | "1.3.6.1.1.8" 40 | ], 41 | "supportedFeatures": [ 42 | "1.3.6.1.1.14", 43 | "1.3.6.1.4.1.4203.1.5.1", 44 | "1.3.6.1.4.1.4203.1.5.2", 45 | "1.3.6.1.4.1.4203.1.5.3", 46 | "1.3.6.1.4.1.4203.1.5.4", 47 | "1.3.6.1.4.1.4203.1.5.5" 48 | ], 49 | "supportedLDAPVersion": [ 50 | "3" 51 | ], 52 | "supportedSASLMechanisms": [ 53 | "DIGEST-MD5", 54 | "NTLM", 55 | "CRAM-MD5", 56 | "LOGIN", 57 | "PLAIN" 58 | ], 59 | "vendorName": [], 60 | "vendorVersion": [] 61 | }, 62 | "type": "DsaInfo" 63 | } -------------------------------------------------------------------------------- /js/icons.js: -------------------------------------------------------------------------------- 1 | import { library, dom } from "@fortawesome/fontawesome-svg-core"; 2 | import { 3 | faCalendarAlt, 4 | faUsers, 5 | faGraduationCap, 6 | faHashtag, 7 | faBirthdayCake, 8 | faMapMarkerAlt, 9 | faClock 10 | } from "@fortawesome/free-solid-svg-icons"; 11 | import { 12 | faFacebookSquare, 13 | faInstagram, 14 | faTwitter, 15 | faGithub, 16 | faDiscord 17 | } from "@fortawesome/free-brands-svg-icons"; 18 | 19 | library.add( 20 | faFacebookSquare, 21 | faInstagram, 22 | faTwitter, 23 | faGithub, 24 | faCalendarAlt, 25 | faUsers, 26 | faGraduationCap, 27 | faHashtag, 28 | faDiscord, 29 | faBirthdayCake, 30 | faMapMarkerAlt, 31 | faClock 32 | ); 33 | 34 | dom.i2svg(); 35 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!venv/bin/python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "apps.csua_backend.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | if "--debug" in sys.argv: 16 | os.environ.setdefault("DJANGO_DEBUG", "1") 17 | sys.argv.remove("--debug") 18 | execute_from_command_line(sys.argv) 19 | -------------------------------------------------------------------------------- /media_root/images/officers/cardigan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/media_root/images/officers/cardigan.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csua-backend", 3 | "version": "1.0.0", 4 | "description": "A backend for the CSUA interblags.", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development --watch", 8 | "build": "webpack --mode production" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/CSUA/csua-backend.git" 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "bugs": { 17 | "url": "https://github.com/CSUA/csua-backend/issues" 18 | }, 19 | "homepage": "https://github.com/CSUA/csua-backend#readme", 20 | "devDependencies": { 21 | "webpack": "^5.94.0", 22 | "webpack-cli": "^4.9.1" 23 | }, 24 | "dependencies": { 25 | "@fortawesome/fontawesome-svg-core": "^1.2.36", 26 | "@fortawesome/free-brands-svg-icons": "^5.15.4", 27 | "@fortawesome/free-regular-svg-icons": "^5.15.4", 28 | "@fortawesome/free-solid-svg-icons": "^5.15.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "csua-backend" 3 | version = "0.1.0" 4 | description = "" 5 | authors = [] 6 | readme = "README.md" 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "django (>=2.2,<3.0)", 10 | "django-markdown-deux (>=1.0.6,<2.0.0)", 11 | "pillow (>=11.2.1,<12.0.0)", 12 | "sorl-thumbnail (==12.10.0)", 13 | "python-decouple (>=3.8,<4.0)", 14 | "django-python3-ldap (>=0.15.8,<0.16.0)", 15 | "ldap3 (==2.8)", 16 | "requests (>=2.32.3,<3.0.0)", 17 | "djangorestframework (>=3.13.1,<4.0.0)", 18 | "slack-bolt (>=1.23.0,<2.0.0)", 19 | "discord-py (>=2.5.2,<3.0.0)", 20 | "schedule (>=1.2.2,<2.0.0)", 21 | "pyfiglet (>=1.0.2,<2.0.0)", 22 | "cowpy (>=1.1.5,<2.0.0)", 23 | "google-api-python-client (>=2.0.0)", 24 | "setuptools (>=79.0.0,<80.0.0)", 25 | "mysqlclient (>=2.2.7,<3.0.0)", 26 | "django-widget-tweaks (>=1.5.0,<2.0.0)", 27 | "gunicorn (>=23.0.0,<24.0.0)", 28 | "django-compressor (==4.4)" 29 | ] 30 | 31 | [tool.poetry] 32 | package-mode = false 33 | 34 | [tool.poetry.group.dev.dependencies] 35 | pre-commit = "^4.2.0" 36 | black = "^25.1.0" 37 | 38 | [build-system] 39 | requires = ["poetry-core>=2.0.0,<3.0.0"] 40 | build-backend = "poetry.core.masonry.api" 41 | -------------------------------------------------------------------------------- /run-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts "u" opt; do 4 | case "$opt" in 5 | u) unix_socket=1 6 | ;; 7 | esac 8 | done 9 | repo_dir=$(dirname $0) 10 | python="${repo_dir}"/venv/bin/python3 11 | port=8000 12 | 13 | # For soda web hosting 14 | if [ "$unix_socket" = "1" ]; then 15 | socket=$HOME/public_html/csua-backend.sock 16 | read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range 17 | port="`shuf -i $LOWERPORT-$UPPERPORT -n 1`" 18 | rm -vf $socket 19 | socat UNIX-LISTEN:$socket,fork,mode=777 TCP:localhost:$port & 20 | echo "Listening on http://csua-backend.$USER.soda.csua.berkeley.edu" 21 | else 22 | if [ $HOSTNAME = "soda" ]; then 23 | echo "For easier use on soda consider using $0 -u" 24 | fi 25 | fi 26 | 27 | "${python}" manage.py runserver localhost:$port 28 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | BRANCH=master 2 | 3 | cd /webserver/csua-backend \ 4 | && git pull origin $BRANCH \ 5 | && venv/bin/pip install -r requirements.txt --upgrade \ 6 | && venv/bin/python manage.py test --keepdb --noinput \ 7 | && venv/bin/python manage.py migrate --noinput \ 8 | && venv/bin/python manage.py collectstatic --noinput \ 9 | && sudo systemctl restart csua-backend-gunicorn \ 10 | && echo done 11 | -------------------------------------------------------------------------------- /scripts/id_rsa.enc: -------------------------------------------------------------------------------- 1 | U2FsdGVkX1/ZM7GzIp9U1GRThEZKNTA0iIRx35/4ust6/gqXKidqGMeQhhXVjbhw 2 | TTF7mMba0TN65cAFBjc6WLLY/JfRKU/PNO/cfRyfvfql6rlDFyRH9tdm4bnNIhjS 3 | dh/YkWi9Opnx1WneZGsd4wFVVZhueaP3bWx9C5qQDeIZxW/kATjvKynYwF2PjvtM 4 | JosZQmUkZSVLfeL0UhY3eo5XWxjOWt6uJh75Zve0XdmmIrplhgJBs8CAAX0/Dhct 5 | tAJqifZUIXK76s1h7P/LqC7MeXB2123L7Ejb2N2RTtdxqeKxmMab4AxziK5L4s0S 6 | LUCIkXoQjJiBs3g/HGIgNQFczMTjdhyrM7iAC9QjHd2zESQc+WznjjBEeVD65BdI 7 | 5ZFZu7lVx3tMm+Mfw5Qv03vwsjtEZDkhNtxhcm4cpvgCFqYPHRFQLD5LZggnyrEW 8 | UDLTd8xqiVeZJYzN03N5ACz8gvK5pHsrCCDI2msx0CWrjBcFfyWCq4WI2fFgW3NR 9 | /lvmqYjCRX94Ey9z2CD7Q20i8vAl0ucgPVdl7T1QFTzPQ67ACkEywGAYeT+HuYLi 10 | Zj/NZUp4m/ZGU+atovtAXCKO9gM3qwqTkX6uRQ3CJBFVZGOHJTFr3u7SL/U5IOQH 11 | p9n49GMN4cYDIGHK3p8LpgYYRQ/ywo3dJ1+xU7zee7lrB+YhvQY6byoTu8eW7Hjf 12 | K0s/ahRBmVsj1wB3mtiCZ3Q3VcJoccGs4rYmv1NQaCTzgPzc+yXjhLe/cnznVxCk 13 | EK63zTOmEByB+1c3/yVJC/sglfDuHCA30pH8xPbp/gb3Ka/2lFQ+Rg+IQhWkmfxf 14 | Z5b5qNAWeh6KCcqlHUOWrUK1Bojaj5636ZnX+cvrcRUyhJg22n2UzhQ9nXWzZ4gs 15 | tfxZ9A8YbrSF++Wwx0GwshQLTwz6QzJ4OAjvAtLUAntxvY0zRPh23oCq8GN//3Fb 16 | aHvK5QhDqM0hLRmRaFpLJRWWLrdFoHRp01IaQrNvBw2VXxmiUgnKSUaVsOutpJNm 17 | 7rjNW4iMtyk4Ikg8mg6FKi47sGZ7eE611RRTA1Doo4nLWCkE1bY09DDQEzDQzgEf 18 | WKg4PfMo+Yiwqs45kJgpGv9C4Se/cZaZDANGtNi0+g/KsuOnrOHPU6j6R298o38L 19 | htbH+gzPbRT13Rci/rGwRKUz7qaIuoWMtkgOjKzVHwnn1m1wgP/hOyTgfd1Tp4A/ 20 | X5j72AIMSQxuLNm+SJDiRe/raxV1FJhQekKv0p6g1TmcWqDgOkQyACcjMaisQIpc 21 | UN6GmbSZv8GqX5dkWSF8cAEL370GKjOwWK8vRG57aglKAp76/Rkv0RBhFcA1wXtK 22 | OQe6FRqWtP7pakU779aVf5L2gG43D8jQU5Y5xkgCsZg0UYJdySgJQKAoDauKjQLa 23 | 5C7kwKlZkCFe1870/clQzdQUslEBxW11YPrXaPRrikGA6p1aSNFlGgfzPIRsj4hC 24 | tYapoKjRvVZoyOHUcIAElNwENDtrA1tknBrzSrOAlIvvcOn5eoQJes+1IcJ4wqKb 25 | lHjBZ3t3GvrKL90tG6ABjc6mzP2/ShjlNzJ+2BWxlyQXgyQIsYMSVxPvRoWua2x5 26 | khC79e8ebXUTRmx76G4kXaXZckzJITqrqgjg9zHUAv75KcyTdHmYYhMWt/wY9cMX 27 | XRQ3KMkr3sFPJwI6DycBYoY1LHDfVIG9OzDzUMrEsWXdp8L78WYSPkDm1YTYHASG 28 | Fj1NDk2zoD8ssZCyVOwli/0h28ZpeHtY+MUZQrguaiKg2T/Ct4ykI5dTQ0GYoUrx 29 | 4lzEMVZ/IY/hS/r+gveAnn72lQ4Xe9DmTynnf+mUR3zLkF1rork1OBeYWwL6jO2F 30 | uyi1dXZ74MZ1WJPrHiRpwwOCd2xAw17YHIY17WMFH5DVeTUapxbDmStko2h/Zz/V 31 | TCWelH/MiQjCG4lQ6h83IYttaqSWfoUIbq151S9DRMGZRia9Z80XOdjtv3jBlv5w 32 | 6yffVAGPNf4nXKco+qwyqAfIF5qIeU8qbw4+DAd4elRmoCJHaiXm0mHLMd2AykEy 33 | nAEYKF6I/BhKPI7jtq8J1eXxwdhf43l8lwKoq34AIBWEdmSLxouidtTbfVa9YdLj 34 | esqgjP2grKS5Mc0fgRB7CTnUdh/bIqj/WqLdafic3uD0yY6SY6Aj7u+g/XbsdDYG 35 | jO1zNzegrbpIa9Lw6aB1aMIfCW1vh1VIqnBM1UBkSzRQmfxN3rKOtvb7fBXfnImR 36 | d0sl1zsxoaUWore5K4FoBg== 37 | -------------------------------------------------------------------------------- /static/css/common.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --accent: #3f64a5; 3 | --light-accent: #8BB8D7; 4 | --bulma-link-text: var(--accent); 5 | --bulma-content-heading-color: var(--accent); 6 | --bulma-light-base: var(--light-accent); 7 | } 8 | 9 | a { 10 | font-weight: bold; 11 | } 12 | 13 | .button { 14 | background-color: var(--accent); 15 | } 16 | 17 | /*.button span {*/ 18 | /* color: white;*/ 19 | /*}*/ 20 | 21 | .navbar a { 22 | font-weight: 500; 23 | } 24 | 25 | #notice { 26 | margin: 10px; 27 | } 28 | 29 | #body-container { 30 | display: flex; 31 | flex-direction: column; 32 | min-height: 100vh; 33 | } 34 | 35 | #content { 36 | flex: 1 0 auto; 37 | } -------------------------------------------------------------------------------- /static/css/computing_resources.css: -------------------------------------------------------------------------------- 1 | code{ 2 | /*background-color: inherit;*/ 3 | color: var(--accent); 4 | } 5 | -------------------------------------------------------------------------------- /static/css/events.css: -------------------------------------------------------------------------------- 1 | .event-old>.dateline::before { 2 | content: "(passed) " 3 | } 4 | .event-old>.dateline { 5 | text-decoration: line-through; 6 | } 7 | #panels { 8 | margin-top: 20px; 9 | border-bottom: 1px solid #777; 10 | } 11 | .panel { 12 | display: none; 13 | } 14 | .tab { 15 | display: inline-block; 16 | background: white; 17 | color: grey; 18 | padding: 10px 5px; 19 | width: 100px; 20 | text-align: center; 21 | border-radius: 4px 4px 0px 0px; 22 | margin-right: 5px; 23 | font-weight: 300; 24 | cursor: pointer; 25 | border: 1px solid #777; 26 | border-bottom: none; 27 | } 28 | .tab:nth-color(1) { 29 | margin-left: 0; 30 | } 31 | .tabhover { 32 | color: black; 33 | background-color: #FAFAFA; 34 | } 35 | .tabcurrent { 36 | background-color: #999; 37 | color: white; 38 | } 39 | 40 | #upcoming-events-gc-container { 41 | position: relative; 42 | padding-bottom: 56.25%; 43 | padding-top: 30px; 44 | height: 0; 45 | overflow: hidden; 46 | } 47 | 48 | #upcoming-events-gc-container iframe { 49 | position: absolute; 50 | top: 0; 51 | left: 0; 52 | width: 100%; 53 | height: 100%; 54 | } 55 | 56 | /* vim: set noet ts=4 sw=4: */ 57 | -------------------------------------------------------------------------------- /static/css/index.css: -------------------------------------------------------------------------------- 1 | #title { 2 | font-size: 4rem; 3 | font-weight: 700; 4 | } 5 | #headline { 6 | text-align: center; 7 | font-weight: 500; 8 | font-size: 1.8rem; 9 | } 10 | @media (max-width: 768px) { 11 | #headline { 12 | font-size: 1.5rem; 13 | } 14 | } 15 | html { 16 | scroll-behavior: smooth; 17 | } 18 | -------------------------------------------------------------------------------- /static/css/officers.css: -------------------------------------------------------------------------------- 1 | /* Logic for showing alternative photo */ 2 | .phototwo, .videotwo { 3 | display: none; 4 | } 5 | .photo-frame:hover .photoone { 6 | display: none; 7 | } 8 | .photo-frame:hover .phototwo, .photo-frame:hover .videotwo { 9 | display: block; 10 | } 11 | 12 | .time-indicator { 13 | position: absolute; 14 | left: 0; 15 | right: 0; 16 | height: 2px; 17 | background-color: red; 18 | display: flex; 19 | align-items: center; 20 | } 21 | 22 | .time-indicator-dot { 23 | width: 8px; 24 | height: 8px; 25 | background-color: red; 26 | border-radius: 50%; 27 | position: absolute; 28 | left: -4px; 29 | } 30 | 31 | .time-cell { 32 | position: relative; 33 | } 34 | 35 | .table { 36 | position: relative; 37 | } 38 | 39 | .officer-button { 40 | background-color: inherit; 41 | } 42 | 43 | .current-oh a { 44 | background-color: var(--accent); 45 | color: white; 46 | } -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/favicon.ico -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /static/icons.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | -------------------------------------------------------------------------------- /static/images/Ben_and_Starr_at_Calapalooza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/Ben_and_Starr_at_Calapalooza.jpg -------------------------------------------------------------------------------- /static/images/CSUA.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/CSUA.jpg -------------------------------------------------------------------------------- /static/images/PuzzleHunt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/PuzzleHunt.jpg -------------------------------------------------------------------------------- /static/images/Woz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/Woz.jpg -------------------------------------------------------------------------------- /static/images/benevolence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/benevolence.png -------------------------------------------------------------------------------- /static/images/capy_and_laptop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/capy_and_laptop.png -------------------------------------------------------------------------------- /static/images/donate1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/donate1.jpg -------------------------------------------------------------------------------- /static/images/donate2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/donate2.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/3mtechtalk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/3mtechtalk.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/bbq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/bbq.png -------------------------------------------------------------------------------- /static/images/events/fall2013/boardgamenight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/boardgamenight.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/codingforgrub.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/codingforgrub.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/gm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/gm2.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/gmone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/gmone.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/gradpanel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/gradpanel.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/hackathon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/hackathon.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/hackathonbar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/hackathonbar.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/hackplus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/hackplus.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/hackplusplus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/hackplusplus.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/hknlan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/hknlan.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/industrypanel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/industrypanel.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/jack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/jack.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/squareworkshop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/squareworkshop.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/startupfair.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/startupfair.jpg -------------------------------------------------------------------------------- /static/images/events/fall2013/zynga.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2013/zynga.jpg -------------------------------------------------------------------------------- /static/images/events/fall2014/gm1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2014/gm1.jpg -------------------------------------------------------------------------------- /static/images/events/fall2014/unixvim.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2014/unixvim.jpg -------------------------------------------------------------------------------- /static/images/events/fall2014/welcomebbq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2014/welcomebbq.jpg -------------------------------------------------------------------------------- /static/images/events/fall2015/hackathonfa15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/fall2015/hackathonfa15.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/abasocial.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/abasocial.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/alumnibbq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/alumnibbq.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/captureflag.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/captureflag.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/crefresher.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/crefresher.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/gm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/gm2.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/gm3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/gm3.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/googlecodejam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/googlecodejam.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/hackathon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/hackathon.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/hackathonbanner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/hackathonbanner.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/hknbplan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/hknbplan.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/intel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/intel.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/merakicfg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/merakicfg.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/oxygencloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/oxygencloud.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/webdesign.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/webdesign.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/welcomesocial.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/welcomesocial.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/workday.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/workday.jpg -------------------------------------------------------------------------------- /static/images/events/spring2014/wozvegas.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/events/spring2014/wozvegas.jpg -------------------------------------------------------------------------------- /static/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/header.png -------------------------------------------------------------------------------- /static/images/icons/logo.svg: -------------------------------------------------------------------------------- 1 | Asset 2 -------------------------------------------------------------------------------- /static/images/icons/twitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/icons/twitch.png -------------------------------------------------------------------------------- /static/images/industry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/industry.jpg -------------------------------------------------------------------------------- /static/images/join.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/join.jpg -------------------------------------------------------------------------------- /static/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/logo.png -------------------------------------------------------------------------------- /static/images/meraki.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/meraki.jpg -------------------------------------------------------------------------------- /static/images/msft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/msft.jpg -------------------------------------------------------------------------------- /static/images/multiblox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/multiblox.png -------------------------------------------------------------------------------- /static/images/officers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/officers.jpg -------------------------------------------------------------------------------- /static/images/pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/pattern.png -------------------------------------------------------------------------------- /static/images/pb_fa18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/pb_fa18.jpg -------------------------------------------------------------------------------- /static/images/pb_sp19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/pb_sp19.jpg -------------------------------------------------------------------------------- /static/images/pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/pic.png -------------------------------------------------------------------------------- /static/images/standing_capy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/standing_capy.png -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330005_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330005_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330006_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330006_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330007_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330007_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330008_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330008_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330009_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330009_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330010_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330010_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330013_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330013_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330014_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330014_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/000045330015_compressed.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/000045330015_compressed.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/Officers_FA16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/Officers_FA16.jpg -------------------------------------------------------------------------------- /static/images/tushar_photoshoots/new_donate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/tushar_photoshoots/new_donate.jpg -------------------------------------------------------------------------------- /static/images/welcome_social_sp19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CSUA/csua-backend/4c6c07924ac0d634af6e0263096bcc0dbdcb9b60/static/images/welcome_social_sp19.jpg -------------------------------------------------------------------------------- /static/js/fb.js: -------------------------------------------------------------------------------- 1 | function timeIsPast(time){ 2 | currentTime = new Date($.now()); 3 | return (currentTime > time); 4 | } 5 | function processEvent(i){ 6 | } 7 | function generateEvent(i, f){ 8 | d = $.data(document, "events")["data"][i]; 9 | FB.api("/" + d.id, function(response){ 10 | $.data(document, "description", response); 11 | //%TODO: Description 12 | //%TODO: Cover photo 13 | }); 14 | return "
" 15 | + "" 16 | + "
" + d.name + "
" 17 | + "
" 18 | // + "
" 19 | // + "" 20 | // + "
" 21 | + "
"+ new Date(d.start_time).toString() 22 | + ", " + d.location + "
" 23 | // + "
" 24 | // + $.data(document, "description") + "
"; 25 | } 26 | window.fbAsyncInit = function() { 27 | FB.init({ 28 | appId : '1491550594450453', 29 | xfbml : true, 30 | cookie : true, 31 | version : 'v2.1' 32 | }); 33 | FB.getLoginStatus(function(response){ 34 | if(!response.authResponse){ 35 | FB.login(function(response){} 36 | , {scope: 'user_groups'}) 37 | } 38 | /* Let's hope this never has to be used. To be deleted 39 | FB.api("/2200089855/events", function(response){ 40 | if(response && !response.error){ 41 | $.data(document, "events", response); 42 | } 43 | } 44 | */ 45 | FB.api("/166402186712773/events", function(response){ 46 | if(response && !response.error){ 47 | $.data(document, "events", response/* + $.data(document, "events")*/); 48 | var i = 0; 49 | while(i < $.data(document, "events")["data"].length && !timeIsPast(new Date($.data(document, "events")["data"][i].start_time))){ 50 | i++; 51 | } 52 | $.data(document, "index", i); 53 | if($.data(document, "index") === 0){ 54 | $(".columnleft").append("No new events for now! Check back soon."); 55 | } 56 | for(i = $.data(document, "index") - 1; i >= 0 ; i--){ 57 | $(".columnleft").append(generateEvent(i, processEvent)); 58 | }; 59 | for(i = 2; i >= 0; i--){ 60 | var j = i + $.data(document, "index"); 61 | if(j < $.data(document, "events")["data"].length){ 62 | $(".columnright").append(generateEvent(j, processEvent)); 63 | } 64 | } 65 | }; 66 | }); 67 | }); 68 | }; 69 | (function(d, s, id){ 70 | var js, fjs = d.getElementsByTagName(s)[0]; 71 | if (d.getElementById(id)) {return;} 72 | js = d.createElement(s); js.id = id; 73 | js.src = "//connect.facebook.net/en_US/sdk.js"; 74 | fjs.parentNode.insertBefore(js, fjs); 75 | }(document, 'script', 'facebook-jssdk')); 76 | 77 | -------------------------------------------------------------------------------- /static/js/jquery.plugin.min.js: -------------------------------------------------------------------------------- 1 | /** Abstract base class for collection plugins v1.0.1. 2 | Written by Keith Wood (kbwood{at}iinet.com.au) December 2013. 3 | Licensed under the MIT (https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt) license. */ 4 | (function(){var j=false;window.JQClass=function(){};JQClass.classes={};JQClass.extend=function extender(f){var g=this.prototype;j=true;var h=new this();j=false;for(var i in f){h[i]=typeof f[i]=='function'&&typeof g[i]=='function'?(function(d,e){return function(){var b=this._super;this._super=function(a){return g[d].apply(this,a||[])};var c=e.apply(this,arguments);this._super=b;return c}})(i,f[i]):f[i]}function JQClass(){if(!j&&this._init){this._init.apply(this,arguments)}}JQClass.prototype=h;JQClass.prototype.constructor=JQClass;JQClass.extend=extender;return JQClass}})();(function($){JQClass.classes.JQPlugin=JQClass.extend({name:'plugin',defaultOptions:{},regionalOptions:{},_getters:[],_getMarker:function(){return'is-'+this.name},_init:function(){$.extend(this.defaultOptions,(this.regionalOptions&&this.regionalOptions[''])||{});var c=camelCase(this.name);$[c]=this;$.fn[c]=function(a){var b=Array.prototype.slice.call(arguments,1);if($[c]._isNotChained(a,b)){return $[c][a].apply($[c],[this[0]].concat(b))}return this.each(function(){if(typeof a==='string'){if(a[0]==='_'||!$[c][a]){throw'Unknown method: '+a;}$[c][a].apply($[c],[this].concat(b))}else{$[c]._attach(this,a)}})}},setDefaults:function(a){$.extend(this.defaultOptions,a||{})},_isNotChained:function(a,b){if(a==='option'&&(b.length===0||(b.length===1&&typeof b[0]==='string'))){return true}return $.inArray(a,this._getters)>-1},_attach:function(a,b){a=$(a);if(a.hasClass(this._getMarker())){return}a.addClass(this._getMarker());b=$.extend({},this.defaultOptions,this._getMetadata(a),b||{});var c=$.extend({name:this.name,elem:a,options:b},this._instSettings(a,b));a.data(this.name,c);this._postAttach(a,c);this.option(a,b)},_instSettings:function(a,b){return{}},_postAttach:function(a,b){},_getMetadata:function(d){try{var f=d.data(this.name.toLowerCase())||'';f=f.replace(/'/g,'"');f=f.replace(/([a-zA-Z0-9]+):/g,function(a,b,i){var c=f.substring(0,i).match(/"/g);return(!c||c.length%2===0?'"'+b+'":':b+':')});f=$.parseJSON('{'+f+'}');for(var g in f){var h=f[g];if(typeof h==='string'&&h.match(/^new Date\((.*)\)$/)){f[g]=eval(h)}}return f}catch(e){return{}}},_getInst:function(a){return $(a).data(this.name)||{}},option:function(a,b,c){a=$(a);var d=a.data(this.name);if(!b||(typeof b==='string'&&c==null)){var e=(d||{}).options;return(e&&b?e[b]:e)}if(!a.hasClass(this._getMarker())){return}var e=b||{};if(typeof b==='string'){e={};e[b]=c}this._optionsChanged(a,d,e);$.extend(d.options,e)},_optionsChanged:function(a,b,c){},destroy:function(a){a=$(a);if(!a.hasClass(this._getMarker())){return}this._preDestroy(a,this._getInst(a));a.removeData(this.name).removeClass(this._getMarker())},_preDestroy:function(a,b){}});function camelCase(c){return c.replace(/-([a-z])/g,function(a,b){return b.toUpperCase()})}$.JQPlugin={createPlugin:function(a,b){if(typeof a==='object'){b=a;a='JQPlugin'}a=camelCase(a);var c=camelCase(b.name);JQClass.classes[c]=JQClass.classes[a].extend(b);new JQClass.classes[c]()}}})(jQuery); -------------------------------------------------------------------------------- /static/js/script.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | 3 | /* Events */ 4 | $('.tab').hover(function() { 5 | $('.tab').removeClass('tabhover'); 6 | $(this).addClass('tabhover'); 7 | }); 8 | $('.tab').click(function() { 9 | $('.tab').removeClass('tabcurrent'); 10 | $(this).addClass('tabcurrent'); 11 | }); 12 | $('#panels a').click(function(event) { 13 | var panel_id = $(this).attr('href'); 14 | $('.panel').hide(); 15 | event.preventDefault(); 16 | $(panel_id).show(); 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /computers/ 3 | Disallow: /admin/ 4 | 5 | # Block Common AI Scraping Bots 6 | User-agent: GPTBot 7 | Disallow: / 8 | User-agent: ChatGPT-User 9 | Disallow: / 10 | User-agent: Google-Extended 11 | Disallow: / 12 | User-agent: PerplexityBot 13 | Disallow: / 14 | User-agent: Amazonbot 15 | Disallow: / 16 | User-agent: ClaudeBot 17 | Disallow: / 18 | User-agent: Omgilibot 19 | Disallow: / 20 | User-Agent: FacebookBot 21 | Disallow: / 22 | User-Agent: Applebot 23 | Disallow: / 24 | User-agent: anthropic-ai 25 | Disallow: / 26 | User-agent: Bytespider 27 | Disallow: / 28 | User-agent: Claude-Web 29 | Disallow: / 30 | User-agent: Diffbot 31 | Disallow: / 32 | User-agent: ImagesiftBot 33 | Disallow: / 34 | User-agent: Omgilibot 35 | Disallow: / 36 | User-agent: Omgili 37 | Disallow: / 38 | User-agent: YouBot 39 | Disallow: / 40 | -------------------------------------------------------------------------------- /templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}404 Not Found{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

404 Not Found

7 |
8 |
9 | 10 |
11 |
12 |      _  _    ___  _  _     _   _       _     _____                     _
13 |     | || |  / _ \| || |   | \ | | ___ | |_  |  ___|__  _   _ _ __   __| |
14 |     | || |_| | | | || |_  |  \| |/ _ \| __| | |_ / _ \| | | | '_ \ / _` |
15 |     |__   _| |_| |__   _| | |\  | (_) | |_  |  _| (_) | |_| | | | | (_| |
16 |        |_|  \___/   |_|   |_| \_|\___/ \__| |_|  \___/ \__,_|_| |_|\__,_|
17 |   
18 |
19 |
20 |
21 |          _______________________________________
22 |         / Sorry, we couldn’t find what you were \
23 |         \ looking for.                          /
24 |          ---------------------------------------
25 |                 \   ^__^
26 |                  \  (xx)\_______
27 |                     (__)\       )\/\
28 |                      U  ||----w |
29 |                         ||     ||
30 |   
31 |
32 | {% endblock %} 33 | {# vim: set et sw=2: #} 34 | -------------------------------------------------------------------------------- /templates/add_officer.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Create or Update Officer{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |
9 | {% if messages %} 10 |
    11 | {% for message in messages %} 12 |
  • {{ message.tags|upper }}: {{ message }}
  • 13 | {% endfor %} 14 |
15 | {% endif %} 16 | 17 |

Create or Update Officer

18 |

Current semester: {{ semester }}

19 |
20 | {% include "form.html" with submit_button_title="Update or Create" %} 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {% endblock %} 29 | {# vim: set et ts=2 sw=2 tw=200: #} 30 | -------------------------------------------------------------------------------- /templates/alumni.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Alumni{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Alumni

7 |
8 |
9 | 10 |
11 |
12 |

13 | CSUA was founded in 1974, and has gone through many generations of 14 | students. 15 | Alumni support the CSUA by connecting students with companies and 16 | opportunities. 17 | Alumni also come to GM2 to present advice for the current generation of 18 | CSUA. 19 |

20 |

21 | CSUA helps connect the alumni body by holding an annual alumni lunch. 22 |

23 | 24 | Alumni: Get connected 25 | 26 |
27 |
28 | {% endblock %} 29 | {# vim: set sw=2 ts=2 et: #} 30 | -------------------------------------------------------------------------------- /templates/archives.html: -------------------------------------------------------------------------------- 1 | {% extends "base_with_hero.html" %} 2 | {% block title %}Archives{% endblock %} 3 | {% block hero %} 4 |
5 | {% include "hero_with_bg_img.html" with title="Archives" size="is-small" %} 6 |
7 | {% endblock %} 8 | {% block page_content %} 9 |
10 |

History

11 |

Current Constitution

12 |

CSUA Documentation written by @ethanhu

13 |

Past Semesters

14 |
15 | {% if sorted_semesters %} 16 |
17 |
18 | {% for semester in sorted_semesters %} 19 |

{{semester.name}}

20 | {% endfor %} 21 |
22 |
23 | {% for semester in sorted_semesters %} 24 |

Officers

25 | {% endfor %} 26 |
27 |
28 | {% for semester in sorted_semesters %} 29 |

Politburo

30 | {% endfor %} 31 |
32 |
33 | {% else %} 34 |

No archives available.

35 | {% endif %} 36 |
37 |
38 | {% endblock %} 39 | -------------------------------------------------------------------------------- /templates/base_with_hero.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% block pre_hero%}{% endblock %} 5 | {% block hero %} 6 | {% include "hero_with_bg_img.html" %} 7 | {% endblock %} 8 | 9 |
10 |
11 | {% block page_content %}{% endblock %} 12 |
13 |
14 | {% endblock %} 15 | {# vim: set et ts=2 sw=2 tw=200: #} 16 | -------------------------------------------------------------------------------- /templates/computers.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Computers{% endblock %} 3 | {% block content %} 4 |
5 |

CSUA KGB Backdoor

6 | 7 | 10 | 13 | 16 | 19 | 22 | {% for computer in computers %} 23 | 24 | 27 | 34 | {% if computer.user is not None %} 35 | 46 | 49 | 52 | {% endif %} 53 | 54 | {% endfor%} 55 |
8 | Computer 9 | 11 | Status 12 | 14 | User 15 | 17 | Realname 18 | 20 | Time Spent 21 |
25 | {{computer.hostname}} 26 | 28 | {% if computer.user is not None %} 29 | Occupied 30 | {% else %} 31 | Available 32 | {% endif %} 33 | 36 | {% if computer.twitchUser != None %} 37 | 38 | {{ computer.user.username }} 39 | 40 | 41 | 42 | {% else %} 43 | {{computer.user.username}} 44 | {% endif %} 45 | 47 | {{computer.user.realname}} 48 | 50 | {{computer.time}} 51 |
56 |
57 | 58 | 61 | 64 | {% for user in users %} 65 | 66 | 76 | 79 | 80 | {% endfor%} 81 |
59 | User 60 | 62 | Time Spent 63 |
67 | {% if user.twitch != None %} 68 | 69 | {{user.username}} 70 | 71 | 72 | {% else %} 73 | {{user.username}} 74 | {% endif %} 75 | 77 | {{user.time}} 78 |
82 | {% endblock %} 83 |
84 | -------------------------------------------------------------------------------- /templates/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "simple_page.html" %} 2 | {% block title %}Contact Us{% endblock %} 3 | {% block pagetitle %}Contact Us{% endblock %} 4 | {% block page_content %} 5 |
6 |

7 | Direct any general questions about the CSUA to pb@csua.berkeley.edu. 8 |

9 |

10 | Please also join our Discord 11 | for updates on club planning and to keep up to date 12 | with day-to-day discussions. 13 |

14 |
15 | {% endblock %} 16 | {# vim: set et ts=2 sw=2: #} 17 | -------------------------------------------------------------------------------- /templates/create_success.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Create Account Success{% endblock %} 3 | {% block css_file %}newuser.css{% endblock %} 4 | {% block content %} 5 |

6 | Success 7 |

8 |

9 | Your account has been created! 10 |

11 |
12 |
13 |
14 |
15 |
16 |  ______________________
17 | < Welcome to the CSUA! >
18 |  ----------------------
19 |   \   ^__^
20 |    \  (oo)\_______
21 |       (__)\       )\/\
22 |           ||----w |
23 |           ||     ||
24 | 
25 |       
26 |
27 |
28 |
29 |
30 |

31 | You can now 32 |

    33 |
  • Log into the desktop machines in our office (311 Soda)
  • 34 |
  • Log into our servers, soda and latte
  • 35 |
  • Receive periodic emails about upcoming events
  • 36 |

    37 |

    38 | If you ever need your password reset, please contact root@csua.berkeley.edu 39 |

    40 |
41 |
42 |
43 | {% endblock %} 44 | {# vim: set et ts=2 sw=2: #} 45 | -------------------------------------------------------------------------------- /templates/events.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Events{% endblock %} 3 | {% block content %} 4 | {% include "hero_with_bg_img.html" with title="Events" subtitle="The CSUA hosts a large variety of social events, tech talks, networking events, workshops, and hackathons for the CS community." size="is-small" %} 5 | {% load calendar_tags %} 6 | {% calendar %} 7 | {% endblock %} 8 | {# vim:set et sw=4: #} 9 | -------------------------------------------------------------------------------- /templates/flatpages/default.html: -------------------------------------------------------------------------------- 1 | {% extends "simple_page.html" %} 2 | {% load markdown_deux_tags %} 3 | {% block title %}{{ flatpage.title }}{% endblock %} 4 | {% block pagetitle %}{{ flatpage.title }}{% endblock %} 5 | {% block page_content %} 6 |
7 | {{ flatpage.content|markdown }} 8 |
9 | {% endblock %} 10 | {# vim: set et ts=2 sw=2: #} 11 | -------------------------------------------------------------------------------- /templates/form.html: -------------------------------------------------------------------------------- 1 | {# make forms with bulma styling #} 2 | {% load widget_tweaks %} 3 | {% load custom_filters %} 4 | {% csrf_token %} 5 | {% for field in form %} 6 |
7 | {{ field|label_with_classes:"label" }} 8 |
9 | {# check that it's not a checkbox before labeling with input #} 10 | {% if field.field.widget.input_type != "checkbox" %} 11 | {{ field|add_class:"input" }} 12 | {% else %} 13 | {{ field|add_class:"checkbox" }} 14 | {% endif %} 15 |
16 | {% if field.help_text %} 17 |

{{ field.help_text }}

18 | {% endif %} 19 | {% for err in field.errors %} 20 |

{{ err }}

21 | {% endfor %} 22 |
23 | {% endfor %} 24 |
25 |
26 | 27 |
28 |
29 | -------------------------------------------------------------------------------- /templates/hero_with_bg_img.html: -------------------------------------------------------------------------------- 1 | {% load thumbnail %} 2 |
15 |
16 |

17 | {{ title }} 18 |

19 |

20 | {{ subtitle }} 21 |

22 |
23 |
24 | -------------------------------------------------------------------------------- /templates/meeting_minutes.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Meeting Minutes{% endblock %} 3 | {% block content %} 4 |
5 |

CSUA Politburo Meeting Minutes

6 |
7 | 8 |
9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /templates/newuser.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Create Account{% endblock %} 3 | {% block css_file %}newuser.css{% endblock %} 4 | {% block content %} 5 |
6 |
7 |

Create Account

8 | 9 |

10 | Hello World! Welcome to the CSUA. By creating an account with us, you will be granted access to our computers and use of our servers. In order to use our servers, ssh into soda.berkeley.edu and enter the username and password you are creating below. 11 |

12 |

13 | Passwords must be at least nine characters long and contain characters from at least two of the following three categories: 14 |

    15 |
  • Alphabetical
  • 16 |
  • Numerical
  • 17 |
  • Punctuation/Other
  • 18 |
19 |

20 |
21 | {% if messages %} 22 |
23 |
    24 | {% for message in messages %} 25 |
  • {{ message }}
  • 26 | {% endfor %} 27 |
28 |
29 | {% endif %} 30 | {% if form %} 31 |
32 | {% if remote %} 33 |
34 | {% if form %} 35 | {% include "form.html" %} 36 | {% endif %} 37 |
38 | {% else %} 39 |
40 | {% include "form.html" %} 41 |
42 | {% endif %} 43 |
44 | {% endif %} 45 |
46 | CSUA Office Rules are as follows: 47 |
    48 |
  1. Do not violate the law or University policy
  2. 49 |
  3. Be courteous to your peers
  4. 50 |
  5. Keep yourself, your surroundings, and the office clean
  6. 51 |
  7. Respect office equipment - put away equipment in designated bins
  8. 52 |
  9. Business hours are from 10 AM to 7 PM 53 |
    Video gaming and full screen videos are not permitted
  10. 54 |
  11. Keep noise pollution to a minimum (use headphones, and other measures)
  12. 55 |
  13. Sleeping in the office is not permitted
  14. 56 |
  15. Be courteous to others who wish to use the computers 57 |
    Limit usage to 2 hours during busy periods
  16. 58 |
59 |
60 |
61 | {% endblock %} 62 | {# vim: set et ts=2 sw=2: #} 63 | -------------------------------------------------------------------------------- /templates/newuser_email.html: -------------------------------------------------------------------------------- 1 | {# leave this here for vim #} 2 | 3 | 4 | Somebody requested a new CSUA account using the email {{ email }}. If this was you, follow the link below: 5 | {% url 'newuser-remote' email=email token=token as theurl %} 6 | https://www.csua.berkeley.edu{{ theurl }} 7 | 8 | 9 | -------------------------------------------------------------------------------- /templates/newuserremoterequest.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Create Account Remotely{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Create Account

7 | 8 |

9 | Hello World! Welcome to the CSUA. By creating an account with us, you will be granted access to our computers and use of our servers. Enter your Berkeley email in order to get sent a link where you can create a new CSUA account. 10 |

11 |

Note: We've recently completed system upgrades and fixes that have been affecting our sign up system recently. Please join our discord at https://csua.org/discord if you are still running into issues.

12 | {% if messages %} 13 |
14 |
    15 | {% for message in messages %} 16 |
  • {{ message }}
  • 17 | {% endfor %} 18 |
19 |
20 | {% endif %} 21 |
22 | {% include "form.html" %} 23 |
24 |
25 |
26 | {% endblock %} 27 | {# vim: set et ts=2 sw=2: #} 28 | 29 | -------------------------------------------------------------------------------- /templates/password_reset/requestpasswordreset.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Request Password Reset{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Request a Password Reset

7 | 8 |

9 | Whoops! It seems you forgot your password. That's okay! We can fix that. Just enter your username and we will send a link to your Berkeley email in which you can reset your password. If you made your account before we asked for your email, please contact root staff. Thank you. 10 |

11 |
12 |
13 | {% include "form.html" %} 14 |
15 |
16 |
17 | {% endblock %} 18 | {# vim:set et sw=4: #} 19 | -------------------------------------------------------------------------------- /templates/password_reset/resetpasswordconfirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Reset Your Password{% endblock %} 3 | {% block content %} 4 |
5 |
6 |

Reset Your Password

7 | 8 |

9 | Enter your new password here to reset your CSUA account password! 10 |

11 |
12 |
13 | {% include "form.html" %} 14 |
15 |
16 |
17 | {% endblock %} 18 | {# vim:set et sw=4: #} 19 | -------------------------------------------------------------------------------- /templates/password_reset/resetsuccess.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Success!{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
7 |

You've successfully changed your password!

8 |
9 |
10 |
11 | {% endblock %} 12 | 13 | {# vim:set et sw=4: #} 14 | -------------------------------------------------------------------------------- /templates/password_reset_email.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Someone asked for password reset for email {{ email }}. Follow the link below: 4 | https://csua.berkeley.edu{% url 'password_reset:reset-password-confirm' uid=uid token=token %} 5 | 6 | 7 | -------------------------------------------------------------------------------- /templates/politburo.html: -------------------------------------------------------------------------------- 1 | {% extends "base_with_hero.html" %} 2 | {% load thumbnail %} 3 | {% block title %}Politburo{% endblock %} 4 | {% block css_file %}politburo.css{% endblock %} 5 | {% block hero %} 6 | {% include "hero_with_bg_img.html" with title="The Politburo" subtitle="Executive Committee of the CSUA" size="is-small" %} 7 | {% endblock %} 8 | {% block page_content %} 9 |
10 |
11 |
12 |
13 |

14 | The Politburo consists of the seven elected officers of the CSUA. 15 | They are the governing body of the CSUA and a unique group of people who are eager to talk to you about the CSUA. 16 | You can contact the Politburo at politburo@csua.berkeley.edu. 17 |

18 |
19 |
20 |
21 | 22 | {% for pb_membership in pb %} 23 | {% with pb_membership.politburo as politburo %} 24 | {% with pb_membership.person.user as user %} 25 |
26 |
27 |
28 | {% thumbnail pb_membership.person.photo1 "500x500" crop="center" as im %} 29 |
30 | 31 |
32 | {% endthumbnail %} 33 |
34 |
35 |
36 |
37 |

{{ user.first_name }} {{ user.last_name }}

38 |

{{ politburo.title }}

39 |

{{ politburo.position }}@csua.berkeley.edu

40 |

41 | {{ politburo.description }} 42 |

43 |

44 | {{ pb_membership.contact_info }} 45 |

46 |
47 |
48 |
49 | {% endwith %} 50 | {% endwith %} 51 | {% endfor %} 52 |
53 |
54 |

Politburo Archive

55 |

For records of past politburo members please see the archives.

56 |
57 |
58 | {% endblock %} 59 | {# vim: set et ts=2 sw=2 tw=0: #} 60 | -------------------------------------------------------------------------------- /templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Profile: {{ username }}{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |

{{ username }}

9 | 10 |

{{ realname }}

11 | 12 |
    13 | {% for group in groups %} 14 |
  • {{ group }}
  • 15 | {% endfor %} 16 |
17 |
18 |
19 | {% endblock %} 20 | {# vim:set sw=2:et:ts=4 #} 21 | -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}Login{% endblock %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |
9 |

Log in

10 |
11 | {% include "form.html" with submit_button_title="Login" %} 12 |
13 |
14 |

15 | Need an account? Create one here!
16 | Forgot your password?
17 | If you need help, please talk to an officer! 18 |

19 |
20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | {# vim: set et ts=2 sw=2 tw=200: #} 26 | -------------------------------------------------------------------------------- /templates/simple_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 |

7 | {% block pagetitle %}DEFAULT TITLE{% endblock %} 8 |

9 |

10 | {{ subtitle }} 11 |

12 |
13 |
14 |
15 |
16 |
17 |
18 | {% block page_content %} 19 | DEFAULT PAGE CONTENT 20 | {% endblock %} 21 |
22 |
23 |
24 |
25 | {% endblock %} 26 | {# vim: set et ts=2 sw=2 tw=200: #} 27 | -------------------------------------------------------------------------------- /templates/sponsors.html: -------------------------------------------------------------------------------- 1 | {% extends "base_with_hero.html" %} 2 | 3 | {% block title %}Industry{% endblock %} 4 | 5 | {% block hero %} 6 | {% include "hero_with_bg_img.html" with title="Sponsors" subtitle="We are grateful for our company sponsors who make much of what we do possible. Thank you!" size="is-small" %} 7 | {% endblock %} 8 | 9 | {% block page_content %} 10 |
11 |
12 |

Industry

13 |

Interested in working with the CSUA to sponsor an event or to speak at 14 | one of our events? Please send an email to indrel@csua.berkeley.edu.

15 |
16 | {% for semester, sponsorships_this_semester in sponsorships_by_semester %} 17 |
18 |

{{ semester.name }}

19 |
20 | {% include "sponsors_grid.html" with sponsorships=sponsorships_this_semester %} 21 | {% endfor %} 22 |
23 | {% endblock %} 24 | {# vim:set sw=2:et:ts=4 #} 25 | -------------------------------------------------------------------------------- /templates/sponsors_grid.html: -------------------------------------------------------------------------------- 1 | {% load thumbnail %} 2 |
3 | {% for sponsorship in sponsorships %} 4 | {% with sponsorship.sponsor as sponsor %} 5 |
6 | 13 |
14 | {% thumbnail sponsor.photo "200x200" as im%} 15 |
16 | 17 |
18 | {% endthumbnail %} 19 |
20 |

{{ sponsorship.description|default_if_none:"" }}

21 |
22 |
23 | {% endwith %} 24 | {% endfor %} 25 |
26 | {# vim:set sw=2:et:ts=4: #} 27 | -------------------------------------------------------------------------------- /templates/tech.html: -------------------------------------------------------------------------------- 1 | {% extends "simple_page.html" %} 2 | {% load static %} 3 | {% block title %}Tech{% endblock %} 4 | {% block pagetitle %}Tech{% endblock %} 5 | {% block page_content %} 6 | {% load staticfiles %} 7 | {% load compress %} 8 | {% compress css %} 9 | 10 | {% endcompress %} 11 |
12 |

13 | CSUA provides computing resources to our members. 14 | A CSUA account is required to log in and use the machines. 15 | Any officer can help you set up an account. 16 | Come visit our office at 311 Soda to make an account, or come to one of our General Meetings. 17 | Questions about computing resources should be directed to root@csua.berkeley.edu 18 |

19 | 20 |

For more technical information about our services, see the Root Staff Wiki

21 | 22 |

23 |

SSH access

24 | Send an SSH key to root@csua.berkeley.edu and after confirmation, log in with your CSUA account to soda.berkeley.edu! There, you will find a fairly standard, secure install of Debian. You can use soda to help host your projects, or as a development server. If you would like some package installed, or need help with using soda, don't hesistate to contact root@csua.berkeley.edu or consult our wiki at this link. More information on setting up SSH here. 25 |

26 | 27 |

28 |

Web hosting

29 | Place your .html files in your ~/public_html directory and they will be publicly available at soda.berkeley.edu/~$USER 30 |

31 | 32 |

33 |

Desktop Machines

34 |

35 | We have high-quality desktop machines for you to use in 311 Soda. Please note one of our office rules: please respect the office and office equipment. Please do not eat or drink over the computing equipment. 36 |

37 | Our machines have been named after drinks since CSUA first christened soda.berkeley.edu. Here are the names of the current desktop machines: 38 |
    39 |
  • Beer (connected to TV)
  • 40 |
  • Boba
  • 41 |
  • Mead
  • 42 |
  • Sake
  • 43 |
  • Scotch
  • 44 |
  • Soju
  • 45 |
  • Vodka
  • 46 |
47 |

48 |

49 |

GPU Computing on Latte

50 | Thanks to donations from NVIDIA, CSUA has a GPU server available for public use. It features 8 NVIDIA Tesla P100 datacenter-class GPUs. 51 |

52 |

53 | Read more about how to access Latte. 54 |

55 |
56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /templates/tutoring.html: -------------------------------------------------------------------------------- 1 | {% extends "simple_page.html" %} 2 | {% block title %}Tutoring{% endblock %} 3 | {% block pagetitle %}Tutoring{% endblock %} 4 | {% block page_content %} 5 | 6 |

7 | Need help with your classes? 8 | Officers of the CSUA host office hours and tutoring services on 9 | 10 | Discord! 11 | 12 |

13 |
14 | 15 |

16 | Most officers are willing to help you out with your classes, root staff work, or just come in and say hi! 17 |

18 |
19 | 20 | 28 | 29 |

30 | If you are interested in becoming a tutor, 31 | 32 | please fill out this form, 33 | 34 | and send an email to president@csua.berkeley.edu. 35 |

36 | {% endblock %} 37 | {# vim:set et sw=4: #} 38 | -------------------------------------------------------------------------------- /templates/workshops.html: -------------------------------------------------------------------------------- 1 | {% extends "base_with_hero.html" %} 2 | {% block title %}Workshops{% endblock %} 3 | {% block hero %} 4 | {% include "hero_with_bg_img.html" with title="Workshops" subtitle="Putting the CS in CSUA" size="is-small" %} 5 | {% endblock %} 6 | {% block page_content %} 7 |
8 |

CSUA has hosted workshops to teach people important skills. Here 9 | are some topics that have been taught.

10 |
    11 |
  • C
  • 12 |
  • Git
  • 13 |
  • LaTeX
  • 14 |
  • Machine Learning / Deep Learning / Computer 15 | Vision
  • 16 |
  • Professional Development (Résumé crafting, 17 | interviewing, personal branding)
  • 18 |
  • Unix
  • 19 |
  • Vim, Tmux
  • 20 |
21 | 22 |

We have a folder on Google docs containing most of our workshops 23 | 24 | here 25 | .

26 | 27 |

We take feedback seriously, and try to make our workshops as 28 | best as they can be. We usually use Google Forms to get 29 | feedback.

30 | 31 | 43 | 44 |

If you have any ideas for workshops, or would like to help host one, 45 | please join the #workshop-planning channel on our 46 | slack. 47 |

48 |
49 | {% endblock %} 50 | {# vim:set et sw=4: #} 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./js/icons.js", 5 | output: { 6 | filename: "icons.js", 7 | path: path.resolve(__dirname, "./static") 8 | } 9 | }; 10 | --------------------------------------------------------------------------------