Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e8dfc7bf88 | |||
| 72bcccfe2f | |||
| 2c457ab531 | |||
| 1120f5eb6e | |||
| f16b77c94b | |||
| 62a813d0ad | |||
| 151de4ebfd | |||
| 199c283b0e | |||
|
60bdf36274
|
|||
|
a7eb9b942b
|
|||
|
7cd1e9b5da
|
|||
|
afd5dbaa9f
|
|||
|
eeb6b856e6
|
|||
|
9ee8bf3376
|
|||
|
848d588bf4
|
|||
| 7ca4b62848 | |||
| 8cd5fd8783 | |||
| afd72ec72b | |||
| 0895e93f6c | |||
| 67ec566728 | |||
|
8737d10a1e
|
|||
|
d9bca7f1ff
|
|||
|
4b9b46f10d
|
|||
|
bafcd88fa1
|
|||
| ac7317a0b7 | |||
| 034b217916 | |||
| ef3f7fa174 | |||
|
17d2ce436a
|
|||
|
6dbc0a2e8e
|
|||
|
43e9045b0a
|
|||
|
5ca9c4222c
|
|||
|
5e8a5c89b3
|
|||
| dc5469fc70 | |||
|
ac19919c51
|
|||
| 7d2b653953 | |||
| 6dea6cc168 | |||
| 75e3476d48 | |||
| c73f85a411 | |||
| 5aba1549d4 | |||
| 3f68e43efd | |||
| b24a2b5254 | |||
| b38f12a6ab | |||
| b07a333459 | |||
| 13e5f319c7 | |||
| 4391a072cc | |||
| 66c9de922d | |||
| 245ae616b1 | |||
| 3afbe17a21 | |||
| 8d3283ef04 | |||
| 8862f7b94c | |||
| 6e5af78586 | |||
| ca731147f2 | |||
| a8dc4d8460 | |||
| 6a0d3f3834 | |||
| 04cdc500b2 | |||
| 8cd7cafdb5 | |||
| ec2fc15a3f | |||
| d7a2382cb5 | |||
| 69d5bfe9a9 | |||
| c555a59cf8 | |||
| d20aecdbe8 | |||
| 57a8415e52 | |||
|
f19b09c5fd
|
|||
|
c6056c385b
|
|||
|
7420cc63fb
|
|||
|
c528c6bacf
|
|||
|
daedd0b068
|
|||
| f83612634d | |||
| b7138e5df8 | |||
| 5f63e59b24 | |||
| a8254095d7 | |||
| dde81c6f9f | |||
| 2e5340e775 | |||
| 92764da0da | |||
|
cbae67c36b
|
|||
|
01f37ac46b
|
|||
|
f39b47b790
|
|||
|
c10d496c57
|
|||
|
cec4fac22f
|
|||
|
a316c92ff9
|
|||
|
c86d9275e7
|
|||
|
fd82639658
|
|||
|
9da1b6c78c
|
|||
|
c5ba2fbcf8
|
|||
|
f49c18f712
|
|||
| c3befbcebd | |||
| 8d25628947 | |||
|
cbaf8cae3a
|
|||
| d05153ce68 | |||
|
9beb3135fb
|
|||
| e251cd7481 | |||
| 0147d683ec | |||
| 3067f2f4ef | |||
| 4db45ad718 | |||
| 849da09805 | |||
| de00f99b8b |
@@ -0,0 +1,26 @@
|
|||||||
|
name: Frontend Next.Js Build Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nextjs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 21.6.2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install frontend-next/
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm --prefix frontend-next/ run lint
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
# Firebase Stuff
|
||||||
|
firebase-admin-key.json
|
||||||
|
firebase*.json
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
@@ -1,2 +1,23 @@
|
|||||||
# COS420-Project
|

|
||||||
Main repo for COS420 Project Group 4
|
Main repo for ChatMaps, our COS420 Project.
|
||||||
|
|
||||||
|
ChatMaps is a web-based social networking service that allows users to connect to others in their local geographic area. It will implement an interactable mapping utility to show general user locations relative to other users, as well as a chat room feature that allows users to start public conversations based on a specified topic. ChatMaps is primarily intended for use in densely populated areas, such as college campuses or metropolitan areas, so people of similar interests can start conversations. The goal of this project is to create a web app that plots locations, gives a radius of the local area, and connects users into different topic-based chat rooms.
|
||||||
|
|
||||||
|
This service will implement user login and profiles, allowing users to add each other as friends and start private conversations. There will be several default chat rooms of varying topics, but users will also have the ability to create their own topics that will be viewable by other users. For example, a user at the University of Maine could create a joinable chat room titled “COS420”, which would be visible to others near this campus.
|
||||||
|
|
||||||
|
This app shares some similarities to other social networks that implement location-based content. ChatMaps’ novel approach is to utilize user location to facilitate real-time communication with others within a given radius.
|
||||||
|
|
||||||
|
The live version of this app can be found at:
|
||||||
|
|
||||||
|
https://chatma.ps/
|
||||||
|
|
||||||
|
A local version can be run with:
|
||||||
|
|
||||||
|
cd frontend-next/
|
||||||
|
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
then navigating to:
|
||||||
|
|
||||||
|
http://localhost:3000
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
node_modules/
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
## Server Update Hook
|
||||||
|
The purpose of this file is to enable automatic server updates when a pull request to the main branch succeeds. This is accomplished via a workflow sent from Github to the server which then reloads both itself and the frontend.
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
Base Url: ```https://chatmaps.nicholaspease.com/api/v1/```
|
||||||
|
|
||||||
|
|Route|Method|Response Type|Use|Responses|
|
||||||
|
|-----|------|-------------|---|---------|
|
||||||
|
|```/``` |GET |Plain Text |Heartbeat|200 - "OK"|
|
||||||
|
|```/deploy```|POST|Plain Text |Server Update Trigger|200 - "OK" - Server Online / Updated
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|File|Purpose|
|
||||||
|
|----|-------|
|
||||||
|
|api.js|NodeJS API using Express|
|
||||||
|
|README.md|API Reference|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Physical Server Update Hook
|
||||||
|
// Imports
|
||||||
|
// Requires express, body-parser, child_process
|
||||||
|
const express = require("express")
|
||||||
|
const bodyParser = require("body-parser")
|
||||||
|
const { exec } = require("child_process")
|
||||||
|
|
||||||
|
// Start the express app and initialize
|
||||||
|
const app = express()
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
app.use(bodyParser.json()); // This processes all the POST data as JSON
|
||||||
|
|
||||||
|
// Uses nodejs and express as "basic" backend server
|
||||||
|
// Recieves webhook from Github at https://chatmaps.nicholaspease.com/
|
||||||
|
app.post("/api/v1/deploy", function (req, res) {
|
||||||
|
// Webhook returns all pull request actions
|
||||||
|
// Limit to "closed", "merged", and the target branch being "main"
|
||||||
|
if (req.body.action == "closed" && req.body.pull_request.merged == true && req.body.pull_request.base.ref == "main") {
|
||||||
|
// (re)Start all the systemd files
|
||||||
|
exec("systemctl restart frontend-next.service", (error, stdout, stderr) => {});
|
||||||
|
exec("systemctl restart server_update_hook.service", (error, stdout, stderr) => {});
|
||||||
|
}
|
||||||
|
res.send("OK")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Generic endpoint, useful to test if updater is alive
|
||||||
|
app.get('/api/v1', (req, res) => {
|
||||||
|
res.send('OK')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Server runs on port 8000, exposed on server at /api/v1
|
||||||
|
app.listen(8000)
|
||||||
+728
@@ -0,0 +1,728 @@
|
|||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"express": "^4.18.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/accepts": {
|
||||||
|
"version": "1.3.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||||
|
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-types": "~2.1.34",
|
||||||
|
"negotiator": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/array-flatten": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
|
},
|
||||||
|
"node_modules/body-parser": {
|
||||||
|
"version": "1.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
|
||||||
|
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.5",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"raw-body": "2.5.2",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bytes": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/call-bind": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"set-function-length": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-disposition": {
|
||||||
|
"version": "0.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "5.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/content-type": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-signature": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "2.6.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/define-data-property": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"gopd": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/depd": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/destroy": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ee-first": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
|
},
|
||||||
|
"node_modules/encodeurl": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-define-property": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-html": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||||
|
},
|
||||||
|
"node_modules/etag": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express": {
|
||||||
|
"version": "4.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||||
|
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"accepts": "~1.3.8",
|
||||||
|
"array-flatten": "1.1.1",
|
||||||
|
"body-parser": "1.20.1",
|
||||||
|
"content-disposition": "0.5.4",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"cookie": "0.5.0",
|
||||||
|
"cookie-signature": "1.0.6",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"finalhandler": "1.2.0",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"merge-descriptors": "1.0.1",
|
||||||
|
"methods": "~1.1.2",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"path-to-regexp": "0.1.7",
|
||||||
|
"proxy-addr": "~2.0.7",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"safe-buffer": "5.2.1",
|
||||||
|
"send": "0.18.0",
|
||||||
|
"serve-static": "1.15.0",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"utils-merge": "1.0.1",
|
||||||
|
"vary": "~1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express/node_modules/body-parser": {
|
||||||
|
"version": "1.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
|
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"content-type": "~1.0.4",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"qs": "6.11.0",
|
||||||
|
"raw-body": "2.5.1",
|
||||||
|
"type-is": "~1.6.18",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8",
|
||||||
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/express/node_modules/raw-body": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/finalhandler": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"unpipe": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/forwarded": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fresh": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-intrinsic": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"has-proto": "^1.0.1",
|
||||||
|
"has-symbols": "^1.0.3",
|
||||||
|
"hasown": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gopd": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||||
|
"dependencies": {
|
||||||
|
"get-intrinsic": "^1.1.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-property-descriptors": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
|
"dependencies": {
|
||||||
|
"es-define-property": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-proto": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-symbols": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-errors": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"inherits": "2.0.4",
|
||||||
|
"setprototypeof": "1.2.0",
|
||||||
|
"statuses": "2.0.1",
|
||||||
|
"toidentifier": "1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.4.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/inherits": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||||
|
},
|
||||||
|
"node_modules/ipaddr.js": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/media-typer": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/merge-descriptors": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
|
||||||
|
},
|
||||||
|
"node_modules/methods": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||||
|
"bin": {
|
||||||
|
"mime": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||||
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/object-inspect": {
|
||||||
|
"version": "1.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||||
|
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/on-finished": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||||
|
"dependencies": {
|
||||||
|
"ee-first": "1.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/parseurl": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-to-regexp": {
|
||||||
|
"version": "0.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
|
||||||
|
},
|
||||||
|
"node_modules/proxy-addr": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||||
|
"dependencies": {
|
||||||
|
"forwarded": "0.2.0",
|
||||||
|
"ipaddr.js": "1.9.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qs": {
|
||||||
|
"version": "6.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
|
||||||
|
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"side-channel": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/range-parser": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/raw-body": {
|
||||||
|
"version": "2.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
|
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
|
||||||
|
"dependencies": {
|
||||||
|
"bytes": "3.1.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"iconv-lite": "0.4.24",
|
||||||
|
"unpipe": "1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
|
},
|
||||||
|
"node_modules/send": {
|
||||||
|
"version": "0.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||||
|
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "2.0.0",
|
||||||
|
"destroy": "1.2.0",
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"etag": "~1.8.1",
|
||||||
|
"fresh": "0.5.2",
|
||||||
|
"http-errors": "2.0.0",
|
||||||
|
"mime": "1.6.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"on-finished": "2.4.1",
|
||||||
|
"range-parser": "~1.2.1",
|
||||||
|
"statuses": "2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/send/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||||
|
},
|
||||||
|
"node_modules/serve-static": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||||
|
"dependencies": {
|
||||||
|
"encodeurl": "~1.0.2",
|
||||||
|
"escape-html": "~1.0.3",
|
||||||
|
"parseurl": "~1.3.3",
|
||||||
|
"send": "0.18.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/set-function-length": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||||
|
"dependencies": {
|
||||||
|
"define-data-property": "^1.1.2",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"get-intrinsic": "^1.2.3",
|
||||||
|
"gopd": "^1.0.1",
|
||||||
|
"has-property-descriptors": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setprototypeof": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||||
|
},
|
||||||
|
"node_modules/side-channel": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.6",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-intrinsic": "^1.2.4",
|
||||||
|
"object-inspect": "^1.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/statuses": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/toidentifier": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/type-is": {
|
||||||
|
"version": "1.6.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||||
|
"dependencies": {
|
||||||
|
"media-typer": "0.3.0",
|
||||||
|
"mime-types": "~2.1.24"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/unpipe": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/utils-merge": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vary": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"express": "^4.18.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
## systemd (systemctl) Files
|
||||||
|
This folder contains all the files required by systemctl on the server for updating and running the backend/frontend. These are copied onto the server and placed in the ```/etc/systemd/system``` folder.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|Filename|Connected Service|
|
||||||
|
|--------|-----------------|
|
||||||
|
|frontend-next.service|Folder: frontend-next|
|
||||||
|
|server_update_hook.service|Folder: backend/server_update_hook|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ChatMaps Frontend Server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
WorkingDirectory=/root/ChatMaps/frontend-next
|
||||||
|
ExecStartPre=/usr/bin/git pull
|
||||||
|
ExecStartPre=/usr/bin/git checkout main
|
||||||
|
ExecStartPre=/usr/bin/npm install
|
||||||
|
ExecStartPre=/usr/bin/npm run build
|
||||||
|
ExecStart=/usr/bin/npm run start
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ChatMaps Backend Server Updater / Restarter
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
WorkingDirectory=/root/ChatMaps/backend/server_update_hook
|
||||||
|
ExecStartPre=/usr/bin/git pull
|
||||||
|
ExecStartPre=/usr/bin/git checkout main
|
||||||
|
ExecStartPre=/usr/bin/npm install
|
||||||
|
ExecStart=/usr/bin/node api.js
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
Generated
+7117
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "chatmaps",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"firebase": "^10.6.0",
|
||||||
|
"firebase-admin": "^12.0.0",
|
||||||
|
"next": "^14.1.0",
|
||||||
|
"pigeon-maps": "^0.21.3",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.50.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.0.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.1.0",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 964 B |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
import { initializeApp, getApps, cert } from "firebase-admin/app";
|
||||||
|
import admin from "firebase-admin";
|
||||||
|
|
||||||
|
export function customInitApp() {
|
||||||
|
if (getApps().length <= 0) {
|
||||||
|
initializeApp({
|
||||||
|
credential: admin.credential.cert({
|
||||||
|
type: process.env.FIREBASE_ADMIN_TYPE,
|
||||||
|
projectId: process.env.FIREBASE_ADMIN_PROJECT_ID,
|
||||||
|
privateKeyId: process.env.FIREBASE_ADMIN_PRIV_KEY_ID,
|
||||||
|
privateKey: process.env.FIREBASE_ADMIN_PRIV_KEY?.replace(/\\n/g, "\n"),
|
||||||
|
clientEmail: process.env.FIREBASE_ADMIN_CLIENT_EMAIL,
|
||||||
|
clientId: process.env.FIREBASE_ADMIN_CLIENT_ID,
|
||||||
|
authUri: process.env.FIREBASE_ADMIN_AUTH_URI,
|
||||||
|
tokenUri: process.env.FIREBASE_ADMIN_TOKEN_URL,
|
||||||
|
authProviderX509CertUrl: process.env.FIREBASE_ADMIN_AUTH_PROVIDER_X509_CERT_URL,
|
||||||
|
clientC509CertUrl: process.env.FIREBASE_ADMIN_CLIENT_X509_CERT_URL,
|
||||||
|
universe_domain: process.env.FIREBASE_ADMIN_UNIVERSE_DOMAIN,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { initializeApp, getApps, getApp } from "firebase/app";
|
||||||
|
import { getAuth } from "firebase/auth";
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
apiKey: "AIzaSyDbDPjQGt-lIjNPeTG-Q5AECM1m0vtOr2c",
|
||||||
|
authDomain: "chatmaps-3e7fa.firebaseapp.com",
|
||||||
|
projectId: "chatmaps-3e7fa",
|
||||||
|
storageBucket: "chatmaps-3e7fa.appspot.com",
|
||||||
|
messagingSenderId: "771010649524",
|
||||||
|
appId: "1:771010649524:web:b6e66d3457820c817b26e1",
|
||||||
|
databaseURL: "https://chatmaps-3e7fa-default-rtdb.firebaseio.com/",
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = getApps().length > 0 ? getApp() : initializeApp(config);
|
||||||
|
var auth = getAuth(app);
|
||||||
|
|
||||||
|
export { auth, app };
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
// Firebase Imports
|
||||||
|
import { auth } from "firebase-admin";
|
||||||
|
import { signInWithEmailAndPassword } from "firebase/auth";
|
||||||
|
// Lib Imports
|
||||||
|
import { app, auth as authConfig } from "../firebase-config";
|
||||||
|
import { customInitApp } from "../firebase-admin";
|
||||||
|
import { getDatabase, ref, get as firebaseGet } from "firebase/database";
|
||||||
|
|
||||||
|
// Needs to "init" on each call to the API
|
||||||
|
customInitApp();
|
||||||
|
|
||||||
|
// Login with Email/Password
|
||||||
|
async function handleEmailAndPassword(email, password) {
|
||||||
|
try {
|
||||||
|
var userCredential = await signInWithEmailAndPassword(authConfig,email,password);
|
||||||
|
if (userCredential.user.accessToken) {
|
||||||
|
var token = await auth().verifyIdToken(userCredential.user.accessToken);
|
||||||
|
var expiresIn = 20 * 60 * 1000; // 20 minutes
|
||||||
|
var sessionCookie = await auth().createSessionCookie(userCredential.user.accessToken, {expiresIn,});
|
||||||
|
if (token) {
|
||||||
|
var database = getDatabase(app)
|
||||||
|
var user = await firebaseGet(ref(database, `users/${userCredential.user.uid}`));
|
||||||
|
if (!user.exists()) {
|
||||||
|
var userOptions = {
|
||||||
|
name: "user",
|
||||||
|
value: JSON.stringify({defined: false, uid: userCredential.user.uid}),
|
||||||
|
maxAge: expiresIn, // 20 mins
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
var userData = user.val()
|
||||||
|
userData.uid = userCredential.user.uid
|
||||||
|
userData.defined = true
|
||||||
|
var userOptions = {
|
||||||
|
name: "user",
|
||||||
|
value: JSON.stringify(userData),
|
||||||
|
maxAge: expiresIn, // 20 mins
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
cookies().set(userOptions);
|
||||||
|
var options = {
|
||||||
|
name: "session",
|
||||||
|
value: sessionCookie,
|
||||||
|
maxAge: expiresIn, // 20 mins
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
};
|
||||||
|
cookies().set(options);
|
||||||
|
cookies().set({
|
||||||
|
name: "uid",
|
||||||
|
value: userCredential.user.uid,
|
||||||
|
maxAge: expiresIn, // 20 mins
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
});
|
||||||
|
return NextResponse.json({ options }, { status: 200 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ error: error.code }, { status: 401 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles POST requests (login requests)
|
||||||
|
export async function POST(req, res) {
|
||||||
|
try {
|
||||||
|
var { email, password } = await req?.json()
|
||||||
|
return await handleEmailAndPassword(email, password); // need session token
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles GET requests (is session still valid requests)
|
||||||
|
export async function GET(req) {
|
||||||
|
var session = cookies().get("session")?.value || "";
|
||||||
|
//Validate if the cookie exist in the request
|
||||||
|
if (!session) {
|
||||||
|
return NextResponse.json({ isLogged: false }, { status: 401 });
|
||||||
|
} else {
|
||||||
|
// Validate session cookie
|
||||||
|
try {
|
||||||
|
var validation = await auth().verifySessionCookie(session, true);
|
||||||
|
return NextResponse.json({ isLogged: true, uid: validation.uid, email: validation.email }, { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ isLogged: false}, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
// Lib Imports
|
||||||
|
import { app } from "../firebase-config";
|
||||||
|
import { getDatabase, ref, set as firebaseSet } from "firebase/database";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
|
|
||||||
|
async function onboard(onboardingJSON, req) {
|
||||||
|
var session = req.cookies.get("session");
|
||||||
|
//Call the authentication endpoint
|
||||||
|
var res = await fetch(new URL("/api/login", req.url), {headers: {Cookie: `session=${session?.value}`}})
|
||||||
|
|
||||||
|
// Login if unauthorized
|
||||||
|
if (res.status !== 200) {
|
||||||
|
return NextResponse.json({}, { status: 401 });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var expiresIn = 20 * 60 * 1000; // 20 minutes
|
||||||
|
var { uid, email } = await res.json()
|
||||||
|
onboardingJSON.email = email
|
||||||
|
onboardingJSON.uid = uid
|
||||||
|
onboardingJSON.defined = true
|
||||||
|
var database = getDatabase(app)
|
||||||
|
await firebaseSet(ref(database, `users/${uid}`), onboardingJSON);
|
||||||
|
var userOptions = {
|
||||||
|
name: "user",
|
||||||
|
value: JSON.stringify(onboardingJSON),
|
||||||
|
maxAge: expiresIn, // 20 mins
|
||||||
|
httpOnly: true,
|
||||||
|
secure: true,
|
||||||
|
};
|
||||||
|
cookies().set(userOptions);
|
||||||
|
return NextResponse.json({}, { status: 200 });
|
||||||
|
} catch(error) {
|
||||||
|
return NextResponse.json({ error: "Internal Server Error: "+error },{ status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Handles POST requests (login requests)
|
||||||
|
export async function POST(req, res) {
|
||||||
|
try {
|
||||||
|
var onboardingJSON = await req?.json()
|
||||||
|
return await onboard(onboardingJSON, req);
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
// Import necessary functions
|
||||||
|
import { createUserWithEmailAndPassword } from "firebase/auth";
|
||||||
|
import { auth } from "../firebase-config";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
// Function to register a new user using Firebase Authentication
|
||||||
|
export async function registerUser(email, password) {
|
||||||
|
try {
|
||||||
|
var userCredential = await createUserWithEmailAndPassword(auth,email,password);
|
||||||
|
// You can perform additional actions after successful registration, if needed.
|
||||||
|
return { success: true, userCredential };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST request handler
|
||||||
|
export async function POST(req, res) {
|
||||||
|
try {
|
||||||
|
// Extract email and password from the request body
|
||||||
|
var { email, password } = await req?.json();
|
||||||
|
// Check if email and password are provided
|
||||||
|
if (!email || !password) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Email and password are required." },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the user
|
||||||
|
try {
|
||||||
|
var userCredential = await createUserWithEmailAndPassword(auth,email,password);
|
||||||
|
return NextResponse.json({message: "Registration successful.",user: userCredential.user,});
|
||||||
|
} catch {
|
||||||
|
return NextResponse.json({ error: registrationResult.error },{ status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Handle unexpected errors
|
||||||
|
return NextResponse.json({ error: "Internal Server Error" },{ status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { cookies } from "next/headers";
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
|
|
||||||
|
export async function GET(req) {
|
||||||
|
cookies().delete('user')
|
||||||
|
cookies().delete('session')
|
||||||
|
cookies().delete('uid')
|
||||||
|
return NextResponse.redirect(new URL("/",req.url))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
|
export async function GET(req) {
|
||||||
|
var userData = cookies().get("user")?.value || false
|
||||||
|
return userData != false? NextResponse.json(JSON.parse(userData)): NextResponse.json({},{status: 203})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps: Home",
|
||||||
|
description: "ChatMaps: Social Media for College Students",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,287 @@
|
|||||||
|
"use client"
|
||||||
|
import { useState, useEffect, createContext, useContext } from 'react'
|
||||||
|
import {Map, Marker, ZoomControl} from "pigeon-maps"
|
||||||
|
import { Form, useForm } from "react-hook-form";
|
||||||
|
import { app } from "../api/firebase-config";
|
||||||
|
import { getDatabase, ref, onValue, get, set} from "firebase/database";
|
||||||
|
var database = getDatabase(app)
|
||||||
|
|
||||||
|
// Data types
|
||||||
|
function Chat({chatObj}) {
|
||||||
|
let dateOptions = {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='width-[100%] bg-white rounded-lg mt-1 text-left p-1 grid grid-cols-2 mr-2'>
|
||||||
|
<div>
|
||||||
|
{chatObj.user}: {chatObj.body}
|
||||||
|
</div>
|
||||||
|
<div className='text-right text-[#d1d1d1]'>
|
||||||
|
{new Date(chatObj.timestamp).toLocaleString(dateOptions)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChatRoomSidebar({roomObj, click}) {
|
||||||
|
return (
|
||||||
|
<div onClick={click} className='border-[black] border-1 shadow-lg p-2 m-2 rounded-lg cursor-pointer'>
|
||||||
|
<div className='col-span-2'>
|
||||||
|
<div className='font-bold'>{roomObj.name}</div>
|
||||||
|
<div className='italic'>{roomObj.description}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function WelcomeMessage() {
|
||||||
|
//TODO: REALLY GROSS WAY TO GET COOKIES, NEED NEW WAY TO STORE USER DATA WITHOUT API CALLS. THIS PAGE HAS TO BE CLIENT SIDE DUE TO MAPS / GEOLOCATION
|
||||||
|
const [data, setData] = useState(null)
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/user')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
setData(data)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
if (isLoading) return <div></div>
|
||||||
|
if (!data) return <div></div>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg m-2 mt-4 text-left p-2 pl-5">
|
||||||
|
<div>
|
||||||
|
Welcome, {data.firstName} {data.lastName} ({data.username})
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Lets see what's happening in your area.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function Geo({loc}) {
|
||||||
|
if (loc) {
|
||||||
|
return (
|
||||||
|
<Map className="rounded-lg" center={[loc.latitude, loc.longitude]} defaultZoom={14}>
|
||||||
|
<Marker width={50} anchor={[loc.latitude, loc.longitude]} color="red"/>
|
||||||
|
<ZoomControl />
|
||||||
|
</Map>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Map className="rounded-lg" defaultCenter={[0, 0]} defaultZoom={14}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Main Tabs
|
||||||
|
function MainTabHome({loc}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WelcomeMessage />
|
||||||
|
<div className='h-[calc(100%-110px)] m-5 rounded-lg'>
|
||||||
|
<Geo loc={loc}/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MainTabChatRoom({room}) {
|
||||||
|
var { register, control, reset, handleSubmit} = useForm()
|
||||||
|
const [chats, setData] = useState(null)
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
|
||||||
|
var user
|
||||||
|
fetch('/api/user')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
user = data
|
||||||
|
})
|
||||||
|
|
||||||
|
var unsubscribeUpdater
|
||||||
|
useEffect(() => {
|
||||||
|
unsubscribeUpdater = onValue(ref(database, `/rooms/${room}/chats`), (snapshot) => {
|
||||||
|
var chatsArr = []
|
||||||
|
var messages = snapshot.val()
|
||||||
|
for (var message in messages) {
|
||||||
|
chatsArr.push(<Chat chatObj={messages[message]} key={messages[message].timestamp}/>)
|
||||||
|
}
|
||||||
|
setData(chatsArr.reverse())
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function sendMessage(data) {
|
||||||
|
reset()
|
||||||
|
var payload = {
|
||||||
|
body: data.message,
|
||||||
|
user: user.username,
|
||||||
|
timestamp: new Date().getTime()
|
||||||
|
}
|
||||||
|
set(ref(database,`/rooms/${room}/chats/${user.username}-${new Date().getTime()}`), payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (isLoading) return <div>Loading</div>
|
||||||
|
if (!chats) return <div>No Chats</div>
|
||||||
|
return (
|
||||||
|
<div className='m-1 h-[100%] rounded-lg'>
|
||||||
|
<div className='h-[90%] m-4 overflow-y-auto flex flex-col-reverse'>
|
||||||
|
{chats}
|
||||||
|
</div>
|
||||||
|
<div className='m-2 h-[10%] w-[100%] bg-white rounded-lg'>
|
||||||
|
<Form onSubmit={handleSubmit(sendMessage)} control={control} className='w-[100%] p-[0px]'>
|
||||||
|
<input type="text" {...register("message")} placeholder="Enter message" className='w-[83%] border-[0px] mt-[8px] mb-[8px]'/>
|
||||||
|
<button className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5 w-[8%]">Send</button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateRoom({loc}) {
|
||||||
|
var { register, control, reset, handleSubmit} = useForm()
|
||||||
|
|
||||||
|
function createRoom(data) {
|
||||||
|
reset()
|
||||||
|
var path = String(loc.latitude.toFixed(2)).replace(".","")+"/"+String(loc.longitude.toFixed(2)).replace(".","")
|
||||||
|
var timestamp = new Date().getTime()
|
||||||
|
var payload = {
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
timestamp: timestamp,
|
||||||
|
latitude: loc.latitude,
|
||||||
|
longitude: loc.longitude,
|
||||||
|
path: path
|
||||||
|
}
|
||||||
|
set(ref(database,`/rooms/${path}/${data.name}-${timestamp}`), payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='overflow-y-auto h-[90%]'>
|
||||||
|
<Form control={control} onSubmit={handleSubmit(createRoom)}>
|
||||||
|
<input {...register("name")} placeholder='Room Name' className='mt-2'/>
|
||||||
|
<input {...register("description")} placeholder='Room Description' className='mt-2'/><br/>
|
||||||
|
<div className='mt-3 mb-2'>
|
||||||
|
Creating room near ({loc.latitude.toFixed(2)}, {loc.longitude.toFixed(2)})
|
||||||
|
</div>
|
||||||
|
<button className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5">Create</button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
var [tab, setTab] = useState("nearby")
|
||||||
|
var [mainTab, setMainTab] = useState("home")
|
||||||
|
var [chatRoom, setChatRoom] = useState("Dev")
|
||||||
|
|
||||||
|
const [myRooms, setRoomData] = useState(null)
|
||||||
|
const [isRoomLoading, setRoomLoading] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/user').then((res) => res.json())
|
||||||
|
.then((user) => {
|
||||||
|
get(ref(database, '/users/'+user.uid+'/rooms')).then((snapshot) => {
|
||||||
|
var rooms = snapshot.val()
|
||||||
|
var roomArr = []
|
||||||
|
for (var room in rooms) {
|
||||||
|
roomArr.push(<ChatRoomSidebar roomObj={rooms[room]} key={rooms[room]} click={() => {setChatRoom(rooms[room].path+"/"+rooms[room].name+"-"+rooms[room].timestamp);setMainTab("chat")}}/>)
|
||||||
|
}
|
||||||
|
setRoomData(roomArr)
|
||||||
|
setRoomLoading(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
|
const [location, setLocation] = useState(null);
|
||||||
|
const [loadingLoc, setLoadingLoc] = useState(true)
|
||||||
|
const [nearby, setNearby] = useState(null);
|
||||||
|
const [loadingNearby, setLoadingNearby] = useState(true);
|
||||||
|
useEffect(() => {
|
||||||
|
if('geolocation' in navigator) {
|
||||||
|
// Retrieve latitude & longitude coordinates from `navigator.geolocation` Web API
|
||||||
|
navigator.geolocation.getCurrentPosition(({ coords }) => {
|
||||||
|
setLocation(coords)
|
||||||
|
setLoadingLoc(false)
|
||||||
|
var nearbyArr = []
|
||||||
|
var path = String(coords.latitude.toFixed(2)).replace(".","")+"/"+String(coords.longitude.toFixed(2)).replace(".","")
|
||||||
|
get(ref(database, `/rooms/${path}`)).then((snapshot) => {
|
||||||
|
if (snapshot.exists()) {
|
||||||
|
var data = snapshot.val()
|
||||||
|
for (var room in data) {
|
||||||
|
nearbyArr.push(<ChatRoomSidebar roomObj={data[room]} click={() => {setChatRoom(data[room].path+"/"+data[room].name+"-"+data[room].timestamp);setMainTab("chat")}}/>)
|
||||||
|
}
|
||||||
|
setLoadingNearby(false)
|
||||||
|
setNearby(nearbyArr)
|
||||||
|
} else {
|
||||||
|
setLoadingNearby(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-4 auto-cols-max overflow-hidden">
|
||||||
|
<div className="col-span-3 h-dvh">
|
||||||
|
<div className="m-2 rounded-lg h-[63px] bg-white shadow-2xl grid grid-cols-2 p-1">
|
||||||
|
<div className='h-[60px]'>
|
||||||
|
<a href="/"><img src="logos/logo_transparent_inverse.png" className='h-[60px]'/></a>
|
||||||
|
</div>
|
||||||
|
<div className='h-[60px] p-4'>
|
||||||
|
{mainTab == "chat" && <a onClick={() => {setMainTab("home")}} className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full mr-5">Close Chat</a>}
|
||||||
|
<a href="/api/signout" className="p-2 cursor-pointer bg-[#dee0e0] bg-cyan-500 text-white font-bold rounded-full">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mr-2 h-[calc(100%-110px)]">
|
||||||
|
{(mainTab == "home" && !loadingLoc) && <MainTabHome loc={location}/>}
|
||||||
|
{(mainTab == "home" && loadingLoc) && <MainTabHome loc={null}/>}
|
||||||
|
{mainTab == "chat" && <MainTabChatRoom room={chatRoom}/>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-dvh">
|
||||||
|
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
|
||||||
|
<div className='p-2'>
|
||||||
|
<div className='p-1 rounded-lg grid grid-cols-3 bg-white'>
|
||||||
|
<div className={tab == "nearby"? 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]': 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]'} onClick={() => {setTab("nearby")}}>Nearby</div>
|
||||||
|
<div className={tab == "rooms"? 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]': 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]'} onClick={() => {setTab("rooms")}}>My Rooms</div>
|
||||||
|
<div className={tab == "create"? 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0] bg-[#D3D3D3]': 'select-none p-1 cursor-pointer rounded-lg hover:bg-[#C0C0C0]'} onClick={() => {setTab("create")}}>Create</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{tab == "nearby" && <div className='overflow-y-auto h-[90%]'>
|
||||||
|
<div>
|
||||||
|
{(!nearby && !loadingNearby) && <div>No Nearby Rooms<br/>Create One?</div>}
|
||||||
|
{loadingNearby && <div>Loading...</div>}
|
||||||
|
{nearby}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{tab == "rooms" && <div className='overflow-y-auto h-[90%]'>
|
||||||
|
<div>
|
||||||
|
{isRoomLoading && <div>Loading</div>}
|
||||||
|
{(!myRooms && !isRoomLoading) && <div>No User Saved Rooms</div>}
|
||||||
|
{myRooms}
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
{(tab == "create" && !loadingLoc) && <CreateRoom loc={location}/>}
|
||||||
|
{(tab == "create" && loadingLoc) && <div>Loading...</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: aliceblue;
|
||||||
|
text-align: center;
|
||||||
|
text-wrap:pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #dee0e0;
|
||||||
|
border-color: black;
|
||||||
|
border: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
margin: 5px;
|
||||||
|
filter: drop-shadow(0 25px 25px rgb(0 0 0 / 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: rgb(166, 166, 166);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 10px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.err {
|
||||||
|
border: 2px solid red;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px 10px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps",
|
||||||
|
description: "ChatMaps: Social Media for College Students"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps: Login",
|
||||||
|
description: "ChatMaps: Social Media for College Students",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"use client";
|
||||||
|
import { useForm, Form } from "react-hook-form";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import "../globals.css"
|
||||||
|
|
||||||
|
function Login() {
|
||||||
|
var router = useRouter();
|
||||||
|
//var { register, handleSubmit } = useForm();
|
||||||
|
var { register, control, setError, formState: { errors, isSubmitting, isSubmitted } } = useForm()
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="grid h-screen place-items-center">
|
||||||
|
<div>
|
||||||
|
<a href="/"><img src="logos/logo_transparent_inverse.png"/></a>
|
||||||
|
<span className="text-[36px]">
|
||||||
|
Chat with friends!
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-[24px] mt-[25px] mb-2">Login</h3>
|
||||||
|
{(errors.email && errors.password) && <div className="text-[red] mb-2 text-[18px] text-bold">Invalid Email or Password.</div>}
|
||||||
|
<Form action="/api/login" encType={'application/json'}
|
||||||
|
onSuccess={() => {
|
||||||
|
router.push("/app");
|
||||||
|
}}
|
||||||
|
onError={() => {
|
||||||
|
const formError = { type: "server", message: "Username or Password Incorrect" }
|
||||||
|
// set same error in both:
|
||||||
|
setError('password', formError)
|
||||||
|
setError('email', formError)
|
||||||
|
}}
|
||||||
|
control={control}
|
||||||
|
>
|
||||||
|
<input type="email" id="email" className={(errors.email && errors.password) && "err"} {...register("email", { required: true })} placeholder="Enter Email Address"/><br/>
|
||||||
|
<input type="password" id="password" name="password" className={(errors.email && errors.password) && "err"} {...register("password", { required: true })} placeholder="Enter Password"/><br/>
|
||||||
|
<button className="inline-flex items-center px-4 py-2 transition ease-in-out duration-150 bg-[#dee0e0] m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
|
||||||
|
{(isSubmitting || isSubmitted) && <span className="inline-block">
|
||||||
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
</span> }
|
||||||
|
Log In
|
||||||
|
</button>
|
||||||
|
<br/>Need an account? <a href="/register">Sign Up</a><br/>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps: Onboarding",
|
||||||
|
description: "ChatMaps: Social Media for College Students"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
"use client";
|
||||||
|
import "../globals.css"
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
|
function Onboarding() {
|
||||||
|
var router = useRouter();
|
||||||
|
var { register, handleSubmit } = useForm();
|
||||||
|
|
||||||
|
async function Onboard(data) {
|
||||||
|
const res = await fetch("/api/onboard", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(data ? data : {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
router.push("/app");
|
||||||
|
} else {
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="grid h-screen place-items-center">
|
||||||
|
<div>
|
||||||
|
<img src="logos/logo_transparent_inverse.png"/>
|
||||||
|
<span className="text-[36px]">
|
||||||
|
Chat with friends!
|
||||||
|
</span>
|
||||||
|
<div className="m-5">
|
||||||
|
Welcome to ChatMaps! We are excited to have you join our community!<br/>First we just need a little bit of information from you to get started.
|
||||||
|
</div>
|
||||||
|
<form action="#" onSubmit={handleSubmit(Onboard)}>
|
||||||
|
<input type="text" {...register("username")} placeholder="Display Name"/><br/>
|
||||||
|
<input type="text" {...register("firstName")} placeholder="First Name"/><br/>
|
||||||
|
<input type="text" {...register("lastName")} placeholder="Last Name"/><br/>
|
||||||
|
<button type="submit" className="bg-[#dee0e0] m-5">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Onboarding;
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
"use client"
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const [statusCode, setData] = useState(null)
|
||||||
|
const [isLoading, setLoading] = useState(true)
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/user')
|
||||||
|
.then((res) => res.status)
|
||||||
|
.then((status) => {
|
||||||
|
setData(status)
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="grid h-screen place-items-center">
|
||||||
|
<div>
|
||||||
|
<img src="logos/logo_transparent_inverse.png"/>
|
||||||
|
<span className="text-[36px]">
|
||||||
|
Chat with friends!
|
||||||
|
</span>
|
||||||
|
<div className="m-5">
|
||||||
|
{(statusCode == 203 || isLoading) &&
|
||||||
|
<div>
|
||||||
|
<a href="/login"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Login</button></a>
|
||||||
|
<a href="/register"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Sign Up</button></a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{statusCode == 200 && <a href="/app"><button className="bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">Continue to App</button></a>}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps: Register",
|
||||||
|
description: "ChatMaps: Social Media for College Students",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
"use client";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useForm, Form } from "react-hook-form";
|
||||||
|
import "../globals.css"
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
|
function Register() {
|
||||||
|
var { register, control, setError, handleSubmit, formState: { errors } } = useForm()
|
||||||
|
var router = useRouter();
|
||||||
|
var emailRegex = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
|
||||||
|
var [passwordMismatch, setPasswordMismatch] = useState(false);
|
||||||
|
|
||||||
|
const passwordMatch = (data) => {
|
||||||
|
return data.password === data.passwordCheck;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = (data) => {
|
||||||
|
if (passwordMatch(data)) {
|
||||||
|
setPasswordMismatch(false);
|
||||||
|
router.push("/success");
|
||||||
|
|
||||||
|
} else{
|
||||||
|
setPasswordMismatch(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="grid h-screen place-items-center">
|
||||||
|
<div>
|
||||||
|
<a href="/"><img src="logos/logo_transparent_inverse.png"/></a>
|
||||||
|
<span className="text-[36px]">
|
||||||
|
Chat with friends!
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-[24px] mt-[15px]">Register</h3>
|
||||||
|
<Form onSubmit={handleSubmit(onSubmit)}
|
||||||
|
onSuccess={() => {
|
||||||
|
router.push("/app");
|
||||||
|
}}
|
||||||
|
action="/api/register"
|
||||||
|
encType={'application/json'}
|
||||||
|
control={control}
|
||||||
|
>
|
||||||
|
<input type="email" {...register("email", {required: true, pattern: emailRegex})} className={errors.email && "err"} placeholder="Enter Email Address"/><br/>
|
||||||
|
<input type="password" {...register("password", {required: true})} className={errors.password && errors.password.type == 'required' && "err"} placeholder="Enter Password"/><br/>
|
||||||
|
<input type ="password" {...register("passwordCheck", {required: false})} className ={errors.passwordCheck && errors.passwordCheck.type == 'required' && "err"} placeholder="Re-enter Password"/><br/>
|
||||||
|
{passwordMismatch && <p className="text-red-500">Passwords do not match</p>}
|
||||||
|
<button type="submit" className="bg-[#dee0e0] m-5 bg-cyan-500 text-white font-bold py-2 px-4 rounded-full">
|
||||||
|
Register</button><br/>
|
||||||
|
Have an account? <a href="/login">Log In</a>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Register;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "../../globals.css";
|
||||||
|
import { Header, Sidebar } from "./shared"
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "ChatMaps",
|
||||||
|
description: "ChatMaps: Social Media for College Students",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
<div className="grid grid-cols-4 auto-cols-max">
|
||||||
|
<div className="col-span-3 h-dvh">
|
||||||
|
<Header/>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<Sidebar/>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
function MessagesDisplayBox() {
|
||||||
|
return (
|
||||||
|
<div className="h-[98%]">
|
||||||
|
Messages Stream in Here
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MessageSendBox() {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg shadow-2xl w-[98%] m-2">
|
||||||
|
Message Sender
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<MessagesDisplayBox/>
|
||||||
|
<MessageSendBox/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export function Header() {
|
||||||
|
return (
|
||||||
|
<div className="m-2 rounded-lg h-[60px] bg-white shadow-2xl">
|
||||||
|
<img src="/logos/logo_transparent_inverse.png" className="h-[60px]"></img>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
return (
|
||||||
|
<div className="h-dvh">
|
||||||
|
<div className="bg-white shadow-2xl rounded-lg m-2 h-[98%]">
|
||||||
|
Sidebar
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// src/middleware.js
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
|
export async function middleware(req, res) {
|
||||||
|
const session = await req.cookies.get("session");
|
||||||
|
if (req.nextUrl.pathname !== "/login" && req.nextUrl.pathname != "/register") {
|
||||||
|
// Login if not logged in
|
||||||
|
if (!session) {
|
||||||
|
return NextResponse.redirect(new URL("/login", req.url));
|
||||||
|
}
|
||||||
|
//Call the authentication endpoint
|
||||||
|
const responseAPI = await fetch(new URL("/api/login", req.url), {
|
||||||
|
headers: {
|
||||||
|
Cookie: `session=${session?.value}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Login if unauthorized
|
||||||
|
if (responseAPI.status !== 200) {
|
||||||
|
return NextResponse.redirect(new URL("/login", req.url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If new user, redirect to onboarding
|
||||||
|
var user = JSON.parse(req.cookies.get("user").value)
|
||||||
|
if (user.defined) {
|
||||||
|
return NextResponse.next();
|
||||||
|
} else {
|
||||||
|
return NextResponse.redirect(new URL("/onboarding", req.url));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Currently in the /login or /register, if user is authenticated, go ahead and direct them to the app
|
||||||
|
if (session) {
|
||||||
|
const responseAPI = await fetch(new URL("/api/login", req.url), {
|
||||||
|
headers: {
|
||||||
|
Cookie: `session=${session?.value}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (responseAPI.status == 200) {
|
||||||
|
return NextResponse.redirect(new URL("/app", req.url))
|
||||||
|
} else {
|
||||||
|
return NextResponse.next() // Unauthenticated, continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NextResponse.next() // Not logged in, direct to login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Protected routes
|
||||||
|
export const config = {
|
||||||
|
matcher: ['/((?!onboarding|api|_next/static|_next/image|auth|favicon.ico|robots.txt|images|logo|$).*)',],
|
||||||
|
missing: [
|
||||||
|
{ type: 'header', key: 'next-router-prefetch' },
|
||||||
|
{ type: 'header', key: 'purpose', value: 'prefetch' },
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "ChatMaps",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user