├── .babelrc
├── .eslintignore
├── .eslintrc.json
├── .github
├── dependabot.yml
└── workflows
│ ├── combine-prs.yml
│ └── node.js.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── api
├── cloud_api
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── email_templates
│ │ ├── blastEmail.js
│ │ ├── passwordReset.js
│ │ ├── test.js
│ │ ├── unsubscribeEmail.js
│ │ └── verification.js
│ ├── routes
│ │ └── Mailer.js
│ ├── server.js
│ └── util
│ │ ├── AuthManager.js
│ │ ├── SceGoogleApiHandler.js
│ │ └── auth.js
├── config
│ └── config.example.json
├── devServer.js
├── main_endpoints
│ ├── Dockerfile
│ ├── Dockerfile.dev
│ ├── models
│ │ ├── Advertisement.js
│ │ ├── OfficeAccessCard.js
│ │ └── User.js
│ ├── routes
│ │ ├── Advertisement.js
│ │ ├── Auth.js
│ │ ├── Cleezy.js
│ │ ├── LedSign.js
│ │ ├── Messages.js
│ │ ├── OfficeAccessCard.js
│ │ ├── Printer.js
│ │ ├── Speaker.js
│ │ ├── User.js
│ │ └── printing
│ │ │ └── keep
│ ├── server.js
│ └── util
│ │ ├── LedSign.js
│ │ ├── Speaker.js
│ │ ├── captcha.js
│ │ ├── emailHelpers.js
│ │ ├── passport.js
│ │ ├── redis-client.js
│ │ ├── token-functions.js
│ │ └── userHelpers.js
├── package-lock.json
├── package.json
└── util
│ ├── CreateUserScript.js
│ ├── GenerateTokenScript.js
│ ├── PathParser.js
│ ├── SceHttpServer.js
│ ├── constants.js
│ ├── logger.js
│ ├── metrics.js
│ └── token-verification.js
├── docker-compose.dev.yml
├── docker-compose.yml
├── docker
├── Dockerfile
├── Dockerfile.dev
└── nginx.conf
├── nginx.conf
├── nginx.dev.conf
├── package-lock.json
├── package.json
├── prometheus
└── prometheus.yml
├── public
├── favicon.ico
├── images
│ └── SCE-glow.png
├── index.html
├── manifest.json
└── output.css
├── setup.py
├── src
├── APIFunctions
│ ├── 2DPrinting.js
│ ├── Advertisement.js
│ ├── ApiResponses.js
│ ├── Auth.js
│ ├── Cleezy.js
│ ├── LedSign.js
│ ├── Mailer.js
│ ├── Messaging.js
│ ├── Profile.js
│ ├── Speaker.js
│ └── User.js
├── Components
│ ├── Background
│ │ ├── background.css
│ │ └── background.jsx
│ ├── Captcha
│ │ └── GoogleRecaptcha.js
│ ├── DecisionModal
│ │ └── ConfirmationModal.js
│ ├── Footer
│ │ └── Footer.js
│ ├── Navbar
│ │ ├── AdminNavbar.js
│ │ ├── NavBarWrapper.js
│ │ └── UserNavbar.js
│ └── Routing
│ │ └── PrivateRoute.js
├── Enums.js
├── Pages
│ ├── 2DPrinting
│ │ ├── 2DPrinting.js
│ │ └── PageSelectDropdown.js
│ ├── About
│ │ └── About.js
│ ├── Advertisement
│ │ └── AdvertisementAdmin.js
│ ├── EmailPreferences
│ │ └── EmailPreferences.js
│ ├── ForgotPassword
│ │ ├── ForgotPassword.js
│ │ └── ResetPassword.js
│ ├── Home
│ │ ├── Home.css
│ │ └── Home.js
│ ├── LedSign
│ │ ├── LedSign.js
│ │ └── ledsign.css
│ ├── Login
│ │ ├── Login.js
│ │ └── LoginInput.js
│ ├── MembershipApplication
│ │ ├── ConfirmationPage.js
│ │ ├── GetPlans.js
│ │ ├── MajorDropdown.js
│ │ ├── MembershipApplication.js
│ │ ├── MembershipForm.js
│ │ └── VerifyEmail.js
│ ├── Messaging
│ │ └── Messaging.js
│ ├── NotFoundPage
│ │ ├── NotFoundPage.js
│ │ └── NotFoundPics
│ │ │ └── empty_room.png
│ ├── Overview
│ │ ├── Overview.js
│ │ ├── PageNumbers.js
│ │ └── SVG.js
│ ├── Profile
│ │ ├── MemberView
│ │ │ ├── ChangePassword.js
│ │ │ ├── DeleteAccountModal.js
│ │ │ ├── GetApiKeyModal.js
│ │ │ ├── InfoCard.js
│ │ │ ├── Profile.js
│ │ │ └── getPicBySeason.js
│ │ └── admin
│ │ │ └── SendUnsubscribeEmail.js
│ ├── Projects
│ │ ├── Components
│ │ │ └── ProjectCard.js
│ │ └── Projects.js
│ ├── Speaker
│ │ └── Speaker.js
│ ├── URLShortener
│ │ ├── SVG.js
│ │ └── URLShortener.js
│ └── UserManager
│ │ ├── EditUserInfo.js
│ │ ├── ExpirationDropdown.js
│ │ └── RoleDropdown.js
├── Routing.js
├── config
│ └── config.example.json
├── index.css
├── index.js
└── input.css
├── tailwind.config.js
├── test
├── api
│ ├── Advertisement.js
│ ├── Auth.js
│ ├── LedSign.js
│ ├── Mailer.js
│ ├── Messages.js
│ ├── OfficeAccessCard.js
│ ├── Speaker.js
│ ├── TokenFunctions.js
│ ├── User.js
│ └── registerUser.js
├── frontend
│ ├── AboutPage.test.js
│ ├── DecisionModal.test.js
│ ├── MessagingModal.test.js
│ └── Routing.test.js
└── util
│ ├── mocks
│ ├── Date.js
│ ├── DiscordApiFunction.js
│ ├── SceSqsApiHandler.js
│ ├── TokenValidFunctions.js
│ └── react-router-dom.js
│ └── tools
│ ├── SceApiTester.js
│ └── tools.js
└── tunnel
├── Dockerfile
├── README.md
└── check_tunnel_status.py
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | "@babel/preset-env"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | api/node_modules/
2 | api/config/
3 | src/config/
4 | node_modules/
5 | build/
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "globals": {
8 | "Atomics": "readonly",
9 | "SharedArrayBuffer": "readonly"
10 | },
11 | "parserOptions": {
12 | "babelOptions": {
13 | "configFile": "./.babelrc"
14 | },
15 | "ecmaFeatures": {
16 | "jsx": true
17 | },
18 | "ecmaVersion": 2018,
19 | "requireConfigFile": "false",
20 | "sourceType": "module"
21 | },
22 | "parser": "@babel/eslint-parser",
23 | "plugins": [
24 | "react"
25 | ],
26 | "rules": {
27 | "indent": [
28 | "error",
29 | 2
30 | ],
31 | "quotes": [
32 | "error",
33 | "single"
34 | ],
35 | "semi": [
36 | "error",
37 | "always"
38 | ],
39 | "camelcase": 2,
40 | "comma-spacing": "error",
41 | "no-console": [
42 | "error",
43 | {
44 | "allow": [
45 | "debug"
46 | ]
47 | }
48 | ],
49 | "no-empty": [
50 | "error",
51 | {
52 | "allowEmptyCatch": true
53 | }
54 | ],
55 | "no-extra-semi": "error",
56 | "eol-last": 2,
57 | "space-before-function-paren": [
58 | "error",
59 | {
60 | "anonymous": "never",
61 | "named": "never",
62 | "asyncArrow": "always"
63 | }
64 | ],
65 | "no-warning-comments": [
66 | 2,
67 | {
68 | "terms": [
69 | "todo",
70 | "fixme",
71 | "any other term"
72 | ],
73 | "location": "anywhere"
74 | }
75 | ],
76 | "spaced-comment": [
77 | 2,
78 | "always",
79 | {
80 | "markers": [
81 | "/"
82 | ]
83 | }
84 | ],
85 | "space-infix-ops": [
86 | "error",
87 | {
88 | "int32Hint": false
89 | }
90 | ],
91 | "no-var": 2,
92 | "no-trailing-spaces": "error",
93 | "brace-style": "error",
94 | "no-use-before-define": "error"
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "daily"
12 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | name: Core-v4 Tests
2 |
3 | on:
4 | pull_request:
5 | branches: [ dev ]
6 | push:
7 | branches: [ dev ]
8 | workflow_dispatch:
9 | branches: [ dev ]
10 |
11 | jobs:
12 | frontend-test:
13 | runs-on: ${{ matrix.os }}
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest]
17 | steps:
18 | - uses: actions/checkout@v2
19 | - name: Use Node.js
20 | uses: actions/setup-node@v2
21 | with:
22 | node-version: 16
23 | # cache: false
24 | - run: python setup.py
25 | - name: cache frontend dependencies
26 | id: frontend-cache
27 | uses: actions/cache@v4
28 | with:
29 | path: ./node_modules
30 | key: modules-${{ hashFiles('package-lock.json') }}
31 | - name: install frontend dependencies if cache miss
32 | if: steps.frontend-cache.outputs.cache-hit != 'true'
33 | run: npm ci --ignore-scripts
34 | - run: npm run frontend-test
35 | api-test:
36 | runs-on: ${{ matrix.os }}
37 | strategy:
38 | matrix:
39 | os: [ubuntu-latest]
40 |
41 | steps:
42 | - uses: actions/checkout@v2
43 | - name: Use Node.js
44 | uses: actions/setup-node@v2
45 | with:
46 | node-version: 18
47 | # cache: false
48 | - run: python setup.py
49 | - name: Starting MongoDB
50 | uses: supercharge/mongodb-github-action@1.3.0
51 | # the frontend dependencies include mocha, we should
52 | # split api and frontend code completely
53 | - name: cache frontend dependencies
54 | id: frontend-cache
55 | uses: actions/cache@v4
56 | with:
57 | path: ./node_modules
58 | key: modules-${{ hashFiles('package-lock.json') }}
59 | - name: install frontend dependencies if cache miss
60 | if: steps.frontend-cache.outputs.cache-hit != 'true'
61 | run: npm ci --ignore-scripts
62 | - name: cache backend dependencies
63 | id: cache-api
64 | uses: actions/cache@v4
65 | with:
66 | path: ./api/node_modules
67 | key: modules-${{ hashFiles('api/package-lock.json') }}
68 | - name: install backend dependencies if cache miss
69 | if: steps.cache-api.outputs.cache-hit != 'true'
70 | run: cd api; npm ci --ignore-scripts; cd ..
71 | - run: npm run api-test
72 | lint:
73 | runs-on: ${{ matrix.os }}
74 | strategy:
75 | matrix:
76 | os: [ubuntu-latest]
77 | steps:
78 | - uses: actions/checkout@v2
79 | - name: Use Node.js
80 | uses: actions/setup-node@v2
81 | with:
82 | node-version: 16
83 | # cache: false
84 | - run: python setup.py
85 | # the frontend dependencies include eslint
86 | - name: cache frontend dependencies
87 | id: frontend-cache
88 | uses: actions/cache@v4
89 | with:
90 | path: ./node_modules
91 | key: modules-${{ hashFiles('package-lock.json') }}
92 | - name: install frontend dependencies if cache miss
93 | if: steps.frontend-cache.outputs.cache-hit != 'true'
94 | run: npm ci --ignore-scripts
95 | - run: npm run lint
96 | build-frontend:
97 | runs-on: ${{ matrix.os }}
98 | strategy:
99 | matrix:
100 | os: [ubuntu-latest]
101 | steps:
102 | - uses: actions/checkout@v2
103 | - name: Use Node.js
104 | uses: actions/setup-node@v2
105 | with:
106 | node-version: 16
107 | # cache: false
108 | - run: python setup.py
109 | # the frontend dependencies include eslint
110 | - name: cache frontend dependencies
111 | id: frontend-cache
112 | uses: actions/cache@v4
113 | with:
114 | path: ./node_modules
115 | key: modules-${{ hashFiles('package-lock.json') }}
116 | - name: install frontend dependencies if cache miss
117 | if: steps.frontend-cache.outputs.cache-hit != 'true'
118 | run: npm ci --ignore-scripts
119 | - run: npm run build
120 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # **** PROJECT: MEANskills Custom ignore options ****
61 | # Ignore all server log files in the log directory EXCEPT the placeholder
62 | log/*
63 | !log/placeholder
64 |
65 | # Ignore credential files (i.e. have the user create their own credentials)
66 | credentials.json
67 | util/common/trustStore/*.key
68 | util/common/trustStore/*.crt
69 | util/common/trustStore/*.csr
70 |
71 | # For now, ignore the security file so that people can name their own CSRs, Certs, etc. their way
72 | util/common/security.js
73 |
74 | .history
75 | .vscode
76 | *.code-workspace
77 | .DS_Store
78 | .prettierrc.js
79 |
80 | build
81 |
82 | # Config files
83 | config.js
84 | config.json
85 | token.json
86 | .env
87 |
88 | # CSR
89 | *.csr
90 | *.key
91 | *.cer
92 |
93 | # File generated by windows during development
94 | **/gp12-pem*
95 | **/is-ci*
96 | **/mime*
97 | **/nodemon*
98 | **/nodetouch*
99 | **/nopt*
100 | **/rc*
101 | **/semver*
102 | **/uuid*
103 | *.cmd
104 | *.ps1
105 |
106 | api/main_endpoints/routes/printing/*
107 | !api/main_endpoints/routes/printing/keep
108 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Core-v4 Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we, as
6 | contributors and maintainers, pledge to making participation in our project
7 | and our community a harassment-free experience — regardless of age, gender,
8 | expression, level of experience, race, religion, or sexual identity and
9 | orientation.
10 |
11 | ## Our Standards
12 |
13 | Examples of behavior that contributes to creating a positive environment
14 | include:
15 |
16 | * Using welcoming and inclusive language
17 | * Being respectful of differing viewpoints and experiences
18 | * Gracefully accepting constructive criticism
19 | * Focusing on what is best for the community
20 | * Showing empathy towards other community members
21 | * Using threads on Slack
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * Trolling, insulting/derogatory comments, and unconstructive criticsm
26 | * Public or private harassment
27 | * Publishing others' private information without explicit permission
28 | * Not using threads on Slack
29 |
30 | ## Our Responsibilities
31 |
32 | Project maintainers are responsible for clarifying the standards of acceptable
33 | behavior and are expected to take appropriate and fair corrective action in
34 | response to any instances of unacceptable behavior.
35 |
36 | Project maintainers have the right and responsibility to remove, edit, or
37 | reject comments, commits, code, wiki edits, issues, and other contributions
38 | that are not aligned to this Code of Conduct, or to ban temporarily or
39 | permanently any contributor for other behaviors that they deem inappropriate,
40 | threatening, offensive, or harmful.
41 |
42 | ## Scope
43 |
44 | This Code of Conduct applies within project space as well as in the SCE Slack
45 | and Discord.
46 |
47 | ## Enforcement
48 |
49 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
50 | reported by contacting to SCE at sce.sjsu@gmail.com. All
51 | complaints will be reviewed and investigated and will result in a response that
52 | is deemed necessary and appropriate to the circumstances.
53 |
54 | Project maintainers who do not follow or enforce the Code of Conduct in good
55 | faith may face temporary or permanent repercussions as determined by other
56 | members of the project's leadership.
57 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 SCE-Development
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Core-v4
2 | Welcome to the respository for the Software and Computer Engineering Society's
3 | offical website!
4 | 
5 | To learn more, check out our
6 | [wiki page](https://github.com/SCE-Development/Core-v4/wiki)!
7 | You can get started running this project locally
8 | [here](https://github.com/SCE-Development/Core-v4/wiki/Getting-Started).
9 |
10 |
--------------------------------------------------------------------------------
/api/cloud_api/Dockerfile:
--------------------------------------------------------------------------------
1 |
2 | FROM node:alpine
3 |
4 | WORKDIR /app
5 |
6 | COPY package.json ./
7 |
8 | RUN npm install --production
9 |
10 | COPY ./cloud_api /app/cloud_api
11 |
12 | COPY ./util /app/util
13 |
14 | COPY ./config /app/config/
15 |
16 | EXPOSE 8082
17 |
18 | ARG GENERAL_API_URL
19 |
20 | ARG DATABASE_HOST
21 |
22 | ARG NODE_ENV
23 |
24 | ARG VERIFICATION_BASE_URL
25 |
26 | CMD [ "node", "./cloud_api/server.js" ]
27 |
--------------------------------------------------------------------------------
/api/cloud_api/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json ./
6 |
7 | RUN npm install --production
8 |
9 | RUN npm install -g nodemon
10 |
11 | COPY ./cloud_api /app/cloud_api
12 |
13 | COPY ./util /app/util
14 |
15 | COPY ./config/config.json /app/config/config.json
16 |
17 | EXPOSE 8082
18 |
19 | ARG GENERAL_API_URL
20 |
21 | ARG NODE_ENV
22 |
23 | ARG VERIFICATION_BASE_URL
24 |
25 | ARG DATABASE_HOST
26 |
27 | CMD ["npm", "run", "cloud_api"]
28 |
--------------------------------------------------------------------------------
/api/cloud_api/email_templates/blastEmail.js:
--------------------------------------------------------------------------------
1 | function blastEmail(user, recipient, subject, content) {
2 | return new Promise((resolve, reject) => {
3 | const sender = `"SCE SJSU 👻" <${user}>`;
4 | return resolve({
5 | from: sender,
6 | to: recipient,
7 | subject: subject,
8 | generateTextFromHTML: true,
9 | html: `
10 | ${content}
11 | `,
12 | });
13 | });
14 | }
15 |
16 | module.exports = { blastEmail };
17 |
--------------------------------------------------------------------------------
/api/cloud_api/email_templates/passwordReset.js:
--------------------------------------------------------------------------------
1 | const generateHashedId = require('../util/auth').generateHashedId;
2 |
3 | function passwordReset(user, resetToken, recipient) {
4 | return new Promise((resolve, reject) => {
5 | generateHashedId(recipient)
6 | .then(hashedId => {
7 | const url =
8 | process.env.VERIFICATION_BASE_URL || 'http://localhost:3000';
9 | const resetLink =
10 | `${url}/reset?id=${hashedId}&resetToken=${resetToken}`;
11 | return resolve({
12 | from: user,
13 | to: recipient,
14 | subject: 'Reset Password for SCE',
15 | generateTextFromHTML: true,
16 | html: `
17 | Hi,
18 |
Click the link below to reset your password. It will expire in 24 hours.
19 | Reset Password
20 | `
21 | });
22 | })
23 | .catch(error => {
24 | reject(error);
25 | });
26 | });
27 | }
28 |
29 | module.exports = { passwordReset };
30 |
--------------------------------------------------------------------------------
/api/cloud_api/email_templates/test.js:
--------------------------------------------------------------------------------
1 | module.exports = function(user, recipient, name) {
2 | return {
3 | from: user,
4 | to: recipient,
5 | subject: 'This is a test email',
6 | generateTextFromHTML: true,
7 | html: `
8 | Hi ${name},
9 | This is a friendly test.
10 | `
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/api/cloud_api/email_templates/unsubscribeEmail.js:
--------------------------------------------------------------------------------
1 | function unsubscribeEmail(user, recipient, name) {
2 | return new Promise((resolve, reject) => {
3 | const url =
4 | process.env.VERIFICATION_BASE_URL || 'http://localhost:3000';
5 | const verifyLink =
6 | `${url}/emailPreferences?user=${recipient}`;
7 | return resolve({
8 | from: user,
9 | to: recipient,
10 | subject: 'Unsubscribe from SCE Emails',
11 | generateTextFromHTML: true,
12 | html: `
13 | Hi ${name || ''},
14 | We appreciate you reducing email spam.
15 | To opt out of all our emails, click the link below
16 | and we’ll take care of the rest.
17 | Update Email Preferences
18 | Thanks,
19 | The Software and Computer Engineering Society
20 | `
21 | });
22 | });
23 | }
24 |
25 | module.exports = { unsubscribeEmail };
26 |
--------------------------------------------------------------------------------
/api/cloud_api/email_templates/verification.js:
--------------------------------------------------------------------------------
1 | const generateHashedId = require('../util/auth').generateHashedId;
2 |
3 | function verification(user, recipient, name) {
4 | return new Promise((resolve, reject) => {
5 | generateHashedId(recipient)
6 | .then(hashedId => {
7 | const url =
8 | process.env.VERIFICATION_BASE_URL || 'http://localhost:3000';
9 | const verifyLink =
10 | `${url}/verify?id=${hashedId}&user=${recipient}`;
11 | return resolve({
12 | from: user,
13 | to: recipient,
14 | subject: 'Please verify your email address',
15 | generateTextFromHTML: true,
16 | html: `
17 | Hi ${name || ''},
18 | Thanks for signing up!
19 | Please verify your email by clicking below.
20 | Verify Email
21 | `
22 | });
23 | })
24 | .catch(error => {
25 | reject(error);
26 | });
27 | });
28 | }
29 |
30 | module.exports = { verification };
31 |
--------------------------------------------------------------------------------
/api/cloud_api/server.js:
--------------------------------------------------------------------------------
1 | const { model } = require('mongoose');
2 | const { SceHttpServer } = require('../util/SceHttpServer');
3 |
4 | function main() {
5 | const API_ENDPOINTS = __dirname + '/routes/';
6 | const cloudServer = new SceHttpServer(API_ENDPOINTS, 8082, '/cloudapi/');
7 | cloudServer.init().then(() => {
8 | cloudServer.openConnection();
9 | });
10 | }
11 |
12 | main();
13 |
--------------------------------------------------------------------------------
/api/cloud_api/util/AuthManager.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const readline = require('readline');
3 | const { consoleColors } = require('../../util/constants');
4 |
5 | const {
6 | redColor,
7 | greenColor,
8 | blueColor,
9 | yellowColor,
10 | defaultColor
11 | } = consoleColors;
12 |
13 | const rl = readline.createInterface({
14 | input: process.stdin,
15 | output: process.stdout,
16 | terminal: false
17 | });
18 |
19 | /**
20 | * Handles the gathering and writing
21 | * of Google API credentials
22 | */
23 | class AuthManager {
24 | /**
25 | * Prompts user for input and resolves the input or false
26 | * @param {String} prompt question to get the info we need
27 | */
28 | inputPrompt(prompt) {
29 | return new Promise((resolve, reject) => {
30 | rl.question(prompt, (ans) => {
31 | if(ans !== '') {
32 | resolve(ans);
33 | } else {
34 | resolve(false);
35 | }
36 | });
37 | });
38 | }
39 |
40 | /**
41 | * Prompts user for client ID & secret and writes them to config
42 | * file containing auth credentials for various google APIs
43 | * @param {String} configPath the directory path of the config.json file
44 | * @param {function} callback a function to run in sync after successful write
45 | */
46 | async setAuthCredentials(configPath, callback) {
47 | console.debug(blueColor +
48 | 'Welcome to SCE\'s Google Cloud API Token Generator!\n'
49 | );
50 | console.debug(
51 | yellowColor +
52 | 'Don\'t know what to do or how this works?\n' +
53 | /* eslint-disable-next-line */
54 | 'Please visit: https://github.com/SCE-Development/Core-v4/wiki/Getting-Started#setting-up-mailer\n' +
55 | defaultColor
56 | );
57 |
58 | const configFileData = fs.readFileSync(configPath);
59 |
60 | if (configFileData) {
61 | let config = JSON.parse(configFileData);
62 |
63 | console.debug(
64 | `Current client ID: ${yellowColor}` +
65 | `${config.googleApiKeys.CLIENT_ID}` +
66 | `${defaultColor}`
67 | );
68 | const clientID = await this.inputPrompt(
69 | 'Please enter the Client ID (press enter to skip): '
70 | );
71 | if (clientID) {
72 | config.googleApiKeys.CLIENT_ID = clientID;
73 | } else {
74 | console.debug(
75 | `defaulting to ${yellowColor}${config.googleApiKeys.CLIENT_ID}`
76 | + defaultColor
77 | );
78 | }
79 |
80 | console.debug(
81 | `Current client secret: ${yellowColor}` +
82 | `${config.googleApiKeys.CLIENT_SECRET}` +
83 | `${defaultColor}`
84 | );
85 | const clientSecret = await this.inputPrompt(
86 | 'Please enter the Client Secret (press enter to skip): '
87 | );
88 | if (clientSecret) {
89 | config.googleApiKeys.CLIENT_SECRET = clientSecret;
90 | } else {
91 | console.debug(
92 | `defaulting to ${yellowColor}${config.googleApiKeys.CLIENT_SECRET}`
93 | + defaultColor
94 | );
95 | }
96 |
97 | fs.writeFile(configPath, JSON.stringify(config), (error) => {
98 | if (error) {
99 | return console.debug(
100 | `A problem occurred trying to write to ${configPath}`, error
101 | );
102 | }
103 |
104 | console.debug(greenColor +
105 | 'Successfully wrote config data to:', configPath + defaultColor
106 | );
107 | callback();
108 | });
109 | } else {
110 | console.debug(redColor +
111 | 'config.json file does not exist at path:', configPath + defaultColor
112 | );
113 | }
114 | }
115 | }
116 |
117 | module.exports = { AuthManager };
118 |
--------------------------------------------------------------------------------
/api/cloud_api/util/auth.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | const GENERAL_API_URL = process.env.GENERAL_API_URL
4 | || 'http://localhost:8080/api';
5 | async function validateVerificationEmail(){
6 | let status = '';
7 | await axios
8 | .post(`${GENERAL_API_URL}/Auth/sendVerificationEmail`)
9 | .then(res =>{
10 | status = res.data;
11 | })
12 | .catch(err => {
13 | status = err.data;
14 | });
15 | return status;
16 | }
17 |
18 | async function generateHashedId(email){
19 | let hashedId = '';
20 | await axios
21 | .post(`${GENERAL_API_URL}/Auth/generateHashedId`, {email})
22 | .then(res =>{
23 | hashedId = res.data.hashedId;
24 | })
25 | .catch(err => {
26 | hashedId = null;
27 | });
28 | return hashedId;
29 | }
30 |
31 | module.exports = {
32 | generateHashedId,
33 | validateVerificationEmail
34 | };
35 |
--------------------------------------------------------------------------------
/api/config/config.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "discordApiKeys": {
3 | "ENABLED": true,
4 | "CLIENT_ID": "NOT_SET",
5 | "CLIENT_SECRET": "NOT_SET",
6 | "REFRESH_TOKEN": "NOT_SET"
7 | },
8 | "googleApiKeys": {
9 | "ENABLED": true,
10 | "CLIENT_ID": "NOT_SET",
11 | "CLIENT_SECRET": "NOT_SET",
12 | "REDIRECT_URIS": [
13 | "https://developers.google.com/oauthplayground"
14 | ],
15 | "REFRESH_TOKEN": "NOT_SET",
16 | "USER": "sce.sjsu@gmail.com",
17 | "CAPTCHA_SECRET_KEY": "NOT_SET"
18 | },
19 | "passwordStrength": "medium",
20 | "PRINTING": {
21 | "ENABLED": false
22 | },
23 | "Cleezy": {
24 | "ENABLED": false
25 | },
26 | "Speakers": {
27 | "ENABLED": false
28 | },
29 | "officeAccessCard": {
30 | "API_KEY": "NOTHING_REALLY"
31 | },
32 | "secretKey": "super duper secret key"
33 | }
--------------------------------------------------------------------------------
/api/devServer.js:
--------------------------------------------------------------------------------
1 | // This if statement checks if the module was require()'d or if it was run
2 | // by node server.js. If we are not requiring it and are running it from the
3 | // command line, we create a server instance and start listening for requests.
4 | if (typeof module !== 'undefined' && !module.parent) {
5 | // Starting servers
6 | require('./main_endpoints/server');
7 | require('./cloud_api/server');
8 | }
9 |
--------------------------------------------------------------------------------
/api/main_endpoints/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json ./
6 |
7 | RUN npm install --production
8 |
9 | COPY ./main_endpoints /app/main_endpoints
10 |
11 | COPY ./util /app/util
12 |
13 | COPY ./config/config.json /app/config/config.json
14 |
15 | EXPOSE 8080
16 |
17 | ARG DISCORD_REDIRECT_URI
18 |
19 | ARG NODE_ENV
20 |
21 | ARG DATABASE_HOST
22 |
23 | CMD [ "node", "./main_endpoints/server.js" ]
24 |
--------------------------------------------------------------------------------
/api/main_endpoints/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM node:alpine
2 |
3 | WORKDIR /app
4 |
5 | COPY package.json ./
6 |
7 | RUN npm install --production
8 | RUN npm install -g nodemon
9 |
10 | COPY ./main_endpoints /app/main_endpoints
11 |
12 | COPY ./util /app/util
13 |
14 | COPY ./config/config.json /app/config/config.json
15 |
16 | EXPOSE 8080
17 |
18 | ARG DISCORD_REDIRECT_URI
19 |
20 | ARG NODE_ENV
21 |
22 | ARG DATABASE_HOST
23 |
24 | CMD [ "npm", "run", "main_endpoints"]
25 |
--------------------------------------------------------------------------------
/api/main_endpoints/models/Advertisement.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const AdvertisementSchema = new Schema(
5 | {
6 | message: {
7 | type: String,
8 | required: true,
9 | maxlength: [255, 'message must be at most 255 characters long']
10 | },
11 | expireDate: {
12 | type: Date,
13 | }
14 | },
15 | { collection: 'Advertisements' }
16 | );
17 |
18 | module.exports = mongoose.model('Advertisement', AdvertisementSchema);
19 |
--------------------------------------------------------------------------------
/api/main_endpoints/models/OfficeAccessCard.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 |
5 | const OfficeAccessCardSchema = new Schema(
6 | {
7 | cardBytes: {
8 | type: String,
9 | required: true,
10 | },
11 | createdAt: {
12 | type: Date,
13 | default: Date.now
14 | },
15 | verifiedCount:{
16 | type:Number,
17 | default:0
18 | },
19 | lastVerified:{
20 | type:Date,
21 | default: Date.now
22 | }
23 | },
24 | { collection: 'OfficeAccessCards' }
25 | );
26 |
27 | module.exports = mongoose.model('OfficeAccessCard', OfficeAccessCardSchema);
28 |
--------------------------------------------------------------------------------
/api/main_endpoints/models/User.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 | const bcrypt = require('bcryptjs');
4 | const membershipState = require('../../util/constants').MEMBERSHIP_STATE;
5 |
6 | const UserSchema = new Schema(
7 | {
8 | firstName: {
9 | type: String,
10 | required: true
11 | },
12 | lastName: {
13 | type: String,
14 | required: true
15 | },
16 | joinDate: {
17 | type: Date,
18 | default: Date.now
19 | },
20 | password: {
21 | type: String,
22 | required: true
23 | },
24 | email: {
25 | type: String,
26 | unique: true,
27 | required: true
28 | },
29 | emailVerified: {
30 | type: Boolean,
31 | default: false
32 | },
33 | emailOptIn: {
34 | type: Boolean,
35 | default: true
36 | },
37 | discordUsername: {
38 | type: String,
39 | default: ''
40 | },
41 | discordDiscrim: {
42 | type: String,
43 | default: ''
44 | },
45 | discordID: {
46 | type: String,
47 | default: ''
48 | },
49 | major: {
50 | type: String
51 | },
52 |
53 | doorCode: {
54 | type: String
55 | },
56 |
57 | accessLevel: {
58 | type: Number,
59 | default: membershipState.PENDING
60 | },
61 | lastLogin: {
62 | type: Date,
63 | default: Date.now
64 | },
65 | membershipValidUntil: {
66 | type: Date,
67 | default: Date.now
68 | },
69 | pagesPrinted: {
70 | type: Number,
71 | default: 0
72 | },
73 | apiKey: {
74 | type: String,
75 | default: ''
76 | }
77 | },
78 | { collection: 'User' }
79 | );
80 |
81 | UserSchema.pre('save', function(next) {
82 | const member = this;
83 | let emailRegExp = new RegExp(['^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0',
84 | '-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61',
85 | '}[a-zA-Z0-9])?)*$'].join(''));
86 | if (!this.email.match(emailRegExp)) {
87 | return next('Bad email tried to be save (email format is: example@domain)');
88 | }
89 | if (this.isModified('password') || this.isNew) {
90 | bcrypt.genSalt(10, function(error, salt) {
91 | if (error) {
92 | return next(error);
93 | }
94 | bcrypt.hash(member.password, salt, function(error, hash) {
95 | if (error) {
96 | return next(error);
97 | }
98 |
99 | member.password = hash;
100 | return next();
101 | });
102 | });
103 | } else {
104 | return next();
105 | }
106 | });
107 |
108 | UserSchema.methods.comparePassword = function(passwd, callback) {
109 | bcrypt.compare(passwd, this.password, function(error, isMatch) {
110 | if (error) {
111 | return callback(error);
112 | }
113 |
114 | callback(null, isMatch);
115 | });
116 | };
117 |
118 | module.exports =
119 | mongoose.models && mongoose.models.User
120 | ? mongoose.models.User
121 | : mongoose.model('User', UserSchema);
122 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/Advertisement.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { OK, BAD_REQUEST, FORBIDDEN, UNAUTHORIZED, NOT_FOUND } = require('../../util/constants').STATUS_CODES;
4 | const {
5 | decodeToken,
6 | checkIfTokenSent,
7 | } = require('../util/token-functions.js');
8 | const Advertisement = require('../models/Advertisement');
9 |
10 | router.get('/', async (req, res) => {
11 | const count = await Advertisement.countDocuments();
12 | const random = Math.floor(Math.random() * count);
13 |
14 | Advertisement.findOne().skip(random)
15 | .then(items => {
16 | res.status(OK).send(items);
17 | })
18 | .catch(error => {
19 | res.sendStatus(BAD_REQUEST);
20 | });
21 | });
22 |
23 | router.get('/getAllAdvertisements', async (req, res) => {
24 | if (!checkIfTokenSent(req)) {
25 | return res.sendStatus(FORBIDDEN);
26 | } else if (!await decodeToken(req)) {
27 | return res.sendStatus(UNAUTHORIZED);
28 | }
29 | Advertisement.find()
30 | .then(items => res.status(OK).send(items))
31 | .catch(error => {
32 | res.sendStatus(BAD_REQUEST);
33 | });
34 | });
35 |
36 | router.post('/createAdvertisement', async (req, res) => {
37 | if (!checkIfTokenSent(req)) {
38 | return res.sendStatus(FORBIDDEN);
39 | } else if (!await decodeToken(req)) {
40 | return res.sendStatus(UNAUTHORIZED);
41 | }
42 | const newAd = new Advertisement({
43 | message: req.body.message,
44 | expireDate: req.body.expireDate
45 | });
46 |
47 | Advertisement.create(newAd)
48 | .then((post) => {
49 | return res.json(post);
50 | })
51 | .catch(
52 | (error) => res.sendStatus(BAD_REQUEST)
53 | );
54 | });
55 |
56 | router.post('/deleteAdvertisement', async (req, res) => {
57 | if (!checkIfTokenSent(req)) {
58 | return res.sendStatus(FORBIDDEN);
59 | } else if (!await decodeToken(req)) {
60 | return res.sendStatus(UNAUTHORIZED);
61 | }
62 | Advertisement.deleteOne({ _id: req.body._id })
63 | .then(result => {
64 | if (result.n < 1) {
65 | res.sendStatus(NOT_FOUND);
66 | } else {
67 | res.sendStatus(OK);
68 | }
69 | })
70 | .catch(() => {
71 | res.sendStatus(BAD_REQUEST);
72 | });
73 | });
74 |
75 | module.exports = router;
76 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/Cleezy.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const axios = require('axios');
3 | const router = express.Router();
4 | const {
5 | decodeToken,
6 | checkIfTokenSent,
7 | } = require('../util/token-functions.js');
8 | const {
9 | OK,
10 | UNAUTHORIZED,
11 | FORBIDDEN,
12 | SERVER_ERROR,
13 | } = require('../../util/constants').STATUS_CODES;
14 | const logger = require('../../util/logger');
15 | const { Cleezy } = require('../../config/config.json');
16 | const { ENABLED } = Cleezy;
17 |
18 | let CLEEZY_URL = process.env.CLEEZY_URL
19 | || 'http://localhost:8000';
20 | let URL_SHORTENER_BASE_URL =
21 | process.env.NODE_ENV === 'production' ? 'https://sce.sjsu.edu/s/' : 'http://localhost:8000/find/';
22 |
23 | router.get('/list', async (req, res) => {
24 | if(!ENABLED) {
25 | return res.json({
26 | disabled: true
27 | });
28 | }
29 | const { page = 0, search, sortColumn = 'created_at', sortOrder = 'DESC'} = req.query;
30 | if (!checkIfTokenSent(req)) {
31 | return res.sendStatus(FORBIDDEN);
32 | } else if (!await decodeToken(req)) {
33 | return res.sendStatus(UNAUTHORIZED);
34 | }
35 | try {
36 | const response = await axios.get(CLEEZY_URL + '/list', {
37 | params: {
38 | page,
39 | ...(search !== undefined && { search }),
40 | // eslint-disable-next-line camelcase
41 | sort_by: sortColumn,
42 | order: sortOrder
43 | },
44 | });
45 | const { data = [], total, rows_per_page: rowsPerPage } = response.data;
46 | const returnData = data.map(element => {
47 | const u = new URL(element.alias, URL_SHORTENER_BASE_URL);
48 | return { ...element, link: u.href };
49 | });
50 | res.json({ data: returnData, total, rowsPerPage });
51 | } catch (err) {
52 | logger.error('/listAll had an error', err);
53 | if (err.response && err.response.data) {
54 | res.status(err.response.status).json({ error: err.response.data });
55 | } else {
56 | res.status(SERVER_ERROR).json({ error: 'Failed to list URLs' });
57 | }
58 | }
59 | });
60 |
61 | router.post('/createUrl', async (req, res) => {
62 | if (!checkIfTokenSent(req)) {
63 | return res.sendStatus(FORBIDDEN);
64 | } else if (!await decodeToken(req)) {
65 | return res.sendStatus(UNAUTHORIZED);
66 | }
67 | const { url, alias } = req.body;
68 | let jsonbody = { url, alias: alias || null };
69 | try {
70 | const response = await axios.post(CLEEZY_URL + '/create_url', jsonbody);
71 | const data = response.data;
72 | const u = new URL( data.alias, URL_SHORTENER_BASE_URL);
73 | res.json({ ...data, link: u });
74 | } catch (err) {
75 | logger.error('/createUrl had an error', err);
76 | res.status(err.response.status).json({ error: err.response.status });
77 | }
78 | });
79 |
80 | router.post('/deleteUrl', async (req, res) => {
81 | if (!checkIfTokenSent(req)) {
82 | return res.sendStatus(FORBIDDEN);
83 | } else if (!await decodeToken(req)) {
84 | return res.sendStatus(UNAUTHORIZED);
85 | }
86 | const { alias } = req.body;
87 | axios
88 | .post(CLEEZY_URL + '/delete/' + alias)
89 | .then(() => {
90 | res.sendStatus(OK);
91 | })
92 | .catch(err => {
93 | logger.error('/deleteUrl had an error', err);
94 | res.status(err.response.status).json({ error: err.response.status });
95 | });
96 | });
97 |
98 | module.exports = router;
99 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/LedSign.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const {
4 | OK,
5 | SERVER_ERROR,
6 | UNAUTHORIZED
7 | } = require('../../util/constants').STATUS_CODES;
8 | const {
9 | decodeToken,
10 | checkIfTokenSent
11 | } = require('../util/token-functions.js');
12 | const logger = require('../../util/logger');
13 | const { updateSign, healthCheck, turnOffSign } = require('../util/LedSign.js');
14 |
15 | const runningInDevelopment = process.env.NODE_ENV !== 'production'
16 | && process.env.NODE_ENV !== 'test';
17 |
18 |
19 | router.get('/healthCheck', async (req, res) => {
20 | /*
21 | * How these work with Quasar:
22 | * https://github.com/SCE-Development/Quasar/wiki/How-do-Health-Checks-Work%3F
23 | */
24 | if (runningInDevelopment) {
25 | return res.sendStatus(OK);
26 | }
27 | const dataFromSign = await healthCheck();
28 | if(!dataFromSign) {
29 | return res.sendStatus(SERVER_ERROR);
30 | }
31 | return res.status(OK).json(dataFromSign);
32 | });
33 |
34 | router.post('/updateSignText', async (req, res) => {
35 | if (!checkIfTokenSent(req)) {
36 | logger.warn('/updateSignText was requested without a token');
37 | return res.sendStatus(UNAUTHORIZED);
38 | }
39 | if (!await decodeToken(req)) {
40 | logger.warn('/updateSignText was requested with an invalid token');
41 | return res.sendStatus(UNAUTHORIZED);
42 | }
43 | if (runningInDevelopment) {
44 | return res.sendStatus(OK);
45 | }
46 | // need to make this its own api endpoint
47 | let result = false;
48 | if (req.body.ledIsOff) {
49 | result = await turnOffSign();
50 | logger.info('turning sign off!');
51 | } else {
52 | logger.info('updating sign with:', req.body);
53 | result = await updateSign(req.body);
54 | }
55 | let status = OK;
56 | if(!result) {
57 | status = SERVER_ERROR;
58 | }
59 | return res.sendStatus(status);
60 | });
61 |
62 |
63 | module.exports = router;
64 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/OfficeAccessCard.js:
--------------------------------------------------------------------------------
1 | const {
2 | UNAUTHORIZED,
3 | BAD_REQUEST,
4 | SERVER_ERROR,
5 | NOT_FOUND,
6 | OK,
7 | } = require('../../util/constants').STATUS_CODES;
8 | const express = require('express');
9 | const router = express.Router();
10 | const bodyParser = require('body-parser');
11 | const OfficeAccessCard = require('../models/OfficeAccessCard.js');
12 | const logger = require('../../util/logger');
13 | const { officeAccessCard = {} } = require('../../config/config.json');
14 | const { API_KEY = 'NOTHING_REALLY' } = officeAccessCard;
15 |
16 | router.use(bodyParser.json());
17 |
18 | function checkIfCardExists(cardBytes) {
19 | return new Promise((resolve) => {
20 | try {
21 | OfficeAccessCard.findOneAndUpdate(
22 | { cardBytes:cardBytes},
23 | {
24 | $inc: { verifiedCount: 1 },
25 | $set: { lastVerified: Date.now() }
26 | }, {
27 | useFindAndModify: false, new:true, upsert:false
28 | }
29 | , (error, result) => {
30 | if (error) {
31 | logger.error('checkIfCardExists got an error querying mongodb: ', error);
32 | return resolve(false);
33 | }
34 | if(!result){
35 | logger.info(`Card:${cardBytes} not found in the database`);
36 | return resolve(false);
37 | }
38 | return resolve(!!result);
39 | });
40 | } catch (error) {
41 | logger.error('checkIfCardExists caught an error: ', error);
42 | return resolve(false);
43 | }
44 | });
45 | }
46 |
47 | router.get('/verify', async (req, res) =>{
48 | const { cardBytes, add = false } = req.query;
49 | const apiKey = req.headers['x-api-key'];
50 | const required = [
51 | { value: apiKey, title: 'X-API-Key HTTP header', },
52 | { value: cardBytes, title: 'cardBytes query parameter', },
53 | ];
54 |
55 | const missingValue = required.find(({ value }) => !value);
56 |
57 | if (missingValue) {
58 | return res.status(BAD_REQUEST).send(` ${missingValue.title} missing from request`);
59 | }
60 |
61 | if (apiKey !== API_KEY) {
62 | return res.sendStatus(UNAUTHORIZED);
63 | }
64 |
65 | const cardExists = await checkIfCardExists(cardBytes);
66 | if (cardExists) {
67 | return res.sendStatus(OK);
68 | }
69 | // if a card doesnt exist and we arent trying
70 | // to add a new one, that means we were trying
71 | // to verify a card, and that card isnt found.
72 | // therefore return a non OK status
73 | if (!add) {
74 | return res.sendStatus(NOT_FOUND);
75 | }
76 |
77 | try {
78 | if (add) {
79 | logger.info('adding a new card');
80 | await new OfficeAccessCard({
81 | cardBytes
82 | }).save();
83 | return res.sendStatus(OK);
84 | }
85 | } catch (error) {
86 | logger.error('Error creating OfficeAccessCard: ', error);
87 | return res.sendStatus(SERVER_ERROR);
88 | }
89 | });
90 |
91 | module.exports = router;
92 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/Printer.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const express = require('express');
3 | const multer = require('multer');
4 | const FormData = require('form-data');
5 | const logger = require('../../util/logger');
6 | const fs = require('fs');
7 | const path = require('path');
8 |
9 | const {
10 | decodeToken,
11 | checkIfTokenSent,
12 | } = require('../util/token-functions.js');
13 | const {
14 | OK,
15 | UNAUTHORIZED,
16 | NOT_FOUND,
17 | SERVER_ERROR,
18 | } = require('../../util/constants').STATUS_CODES;
19 | const {
20 | PRINTING = {}
21 | } = require('../../config/config.json');
22 | const { MetricsHandler } = require('../../util/metrics');
23 |
24 | // see https://github.com/SCE-Development/Quasar/tree/dev/docker-compose.dev.yml#L11
25 | let PRINTER_URL = process.env.PRINTER_URL
26 | || 'http://localhost:14000';
27 |
28 | const router = express.Router();
29 |
30 | // stores file inside temp folder
31 | const storage = multer.diskStorage({
32 | destination: function(req, file, cb) {
33 | cb(null, path.join(__dirname, 'printing'));
34 | },
35 | filename: function(req, file, cb) {
36 | const uniqueSuffix = Date.now();
37 | cb(null, uniqueSuffix + '_' + file.originalname);
38 | }
39 | });
40 |
41 | const upload = multer({ storage });
42 |
43 | router.get('/healthCheck', async (req, res) => {
44 | /*
45 | * How these work with Quasar:
46 | * https://github.com/SCE-Development/Quasar/wiki/How-do-Health-Checks-Work%3F
47 | */
48 | if (!PRINTING.ENABLED) {
49 | logger.warn('Printing is disabled, returning 200 to mock the printing server');
50 | return res.sendStatus(OK);
51 | }
52 | await axios
53 | .get(PRINTER_URL + '/healthcheck/printer')
54 | .then(() => {
55 | return res.sendStatus(OK);
56 | })
57 | .catch((err) => {
58 | logger.error('Printer SSH tunnel is down: ', err);
59 | MetricsHandler.sshTunnelErrors.inc({ type: 'Printer' });
60 | return res.sendStatus(NOT_FOUND);
61 | });
62 | });
63 |
64 | router.post('/sendPrintRequest', upload.single('file'), async (req, res) => {
65 | if (!checkIfTokenSent(req)) {
66 | logger.warn('/sendPrintRequest was requested without a token');
67 | return res.sendStatus(UNAUTHORIZED);
68 | }
69 | if (!await decodeToken(req)) {
70 | logger.warn('/sendPrintRequest was requested with an invalid token');
71 | return res.sendStatus(UNAUTHORIZED);
72 | }
73 | if (!PRINTING.ENABLED) {
74 | logger.warn('Printing is disabled, returning 200 to mock the printing server');
75 | return res.sendStatus(OK);
76 | }
77 | const { copies, sides } = req.body;
78 | const file = req.file;
79 | const data = new FormData();
80 | data.append('file', fs.createReadStream(file.path), { filename: file.originalname });
81 | data.append('copies', copies);
82 | data.append('sides', sides);
83 | axios.post(PRINTER_URL + '/print',
84 | data,
85 | {
86 | headers: {
87 | ...data.getHeaders(),
88 | }
89 | })
90 | .then(() => {
91 | // delete file from temp folder after printing
92 | fs.unlink(file.path, (err) => {
93 | if (err) {
94 | logger.error(`Unable to delete file at path ${file.path}:`, err);
95 | }
96 | });
97 | res.sendStatus(OK);
98 | }).catch((err) => {
99 | logger.error('/sendPrintRequest had an error: ', err);
100 | res.sendStatus(SERVER_ERROR);
101 | });
102 | });
103 |
104 | module.exports = router;
105 |
--------------------------------------------------------------------------------
/api/main_endpoints/routes/printing/keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SCE-Development/Clark/46801ff13fd8842b17e29fdd99094097800292da/api/main_endpoints/routes/printing/keep
--------------------------------------------------------------------------------
/api/main_endpoints/server.js:
--------------------------------------------------------------------------------
1 | const { SceHttpServer } = require('../util/SceHttpServer');
2 |
3 | function main() {
4 | const API_ENDPOINTS = [
5 | __dirname + '/routes/'
6 | ];
7 | const mainEndpointServer = new SceHttpServer(API_ENDPOINTS, 8080);
8 | mainEndpointServer.init().then(() => {
9 | mainEndpointServer.openConnection();
10 | });
11 | }
12 |
13 | main();
14 |
--------------------------------------------------------------------------------
/api/main_endpoints/util/LedSign.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 | const logger = require('../../util/logger');
3 | const { MetricsHandler } = require('../../util/metrics');
4 |
5 |
6 | // see https://github.com/SCE-Development/rpi-led-controller/tree/master/server.py#L126
7 | let LED_SIGN_URL = process.env.LED_SIGN_URL
8 | || 'http://localhost';
9 |
10 | /**
11 | * These functions are meant only for use in production, where the
12 | * LED sign can be reached from Core-v4 through an SSH tunnel. Want
13 | * to learn more about how this works? Check out the below link:
14 | * https://github.com/SCE-Development/Quasar/wiki/How-Does-the-LED-Sign-Work%3F
15 | */
16 |
17 | /**
18 | * Send an object to update the LED sign with.
19 | * @param {Object} data The new sign data, for example
20 | * {
21 | * "scrollSpeed": 15,
22 | * "backgroundColor": "#0000FF",
23 | * "textColor": "#00FF00",
24 | * "borderColor": "#FF0000",
25 | * "text": "Welcome to SCE!",
26 | * }
27 | * @returns {Promise} Whether the LED Sign's API can be reached or not.
28 | */
29 | async function updateSign(data) {
30 | return new Promise((resolve) => {
31 | axios
32 | .post(LED_SIGN_URL + '/api/update-sign', data)
33 | .then(() => {
34 | resolve(true);
35 | }).catch((err) => {
36 | logger.error('updateSign had an error: ', err);
37 | resolve(false);
38 | });
39 | });
40 | }
41 |
42 | /**
43 | * Turn the led sign off using its REST API.
44 | * @returns {Promise