This commit is contained in:
Troplo 2022-07-29 20:34:27 +10:00
parent 98218285ef
commit b95676624a
85 changed files with 1562 additions and 66 deletions

View file

@ -1,10 +0,0 @@
VUE_APP_VERSION=[AIV]{version}[/AIV]
VUE_APP_BUILD_DATE=[AIV]{date}[/AIV]
VUE_APP_SENTRY_DSN=https://c82c997e304148cf8deed40fef8912ea@o444992.ingest.sentry.io/6307174
VUE_APP_MATOMO_SITE_ID=5
VUE_APP_MATOMO_URL=https://analytics.flowinity.com
VUE_APP_MATOMO_TRACKER=flow
VUE_APP_MATOMO_DOMAINS=*.troplo.com
VUE_APP_MATOMO_ENABLED=true
VUE_APP_SENTRY_ENABLED=true
VUE_APP_BASE_URL=

28
.gitignore vendored
View file

@ -1,29 +1 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
.env
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea .idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.sentryclirc
backend/node_modules
backend/config/config.json
backend/.env

View file

@ -2,13 +2,56 @@
![Wakatime](https://wakatime.troplo.com/api/badge/Troplo/interval:any/project:Colubrina?label=wakatime) ![Wakatime](https://wakatime.troplo.com/api/badge/Troplo/interval:any/project:Colubrina?label=wakatime)
Colubrina is a simple chatting platform written in Vue, and Vuetify for the frontend, and Node.js, Sequelize and Socket.io for the backend.
### Checklist
- [x] Messaging
- [x] Authentication
- [x] Admin panel
- [x] CLI (cli)
- [ ] Message history
- [x] User profile cards
- [x] Group creation and modification
- [x] Friending
- [x] Searching
<img src="https://i.troplo.com/i/d608273e066c.png" alt="Chat" width="45%"></img> <img src="https://i.troplo.com/i/d608273e066c.png" alt="Chat" width="45%"></img>
<img src="https://i.troplo.com/i/e8e2c9d6e349.png" alt="Friends" width="45%"></img> <img src="https://i.troplo.com/i/e8e2c9d6e349.png" alt="Friends" width="45%"></img>
<img src="https://i.troplo.com/i/e958b8e58c5e.png" alt="Chat with AMOLED theme" width="45%"></img> <img src="https://i.troplo.com/i/e958b8e58c5e.png" alt="Chat with AMOLED theme" width="45%"></img>
<img src="https://i.troplo.com/i/279376da3f1d.png" alt="Chat with profile card and light theme" width="45%"></img> <img src="https://i.troplo.com/i/279376da3f1d.png" alt="Chat with profile card and light theme" width="45%"></img>
<img src="https://i.troplo.com/i/59b63d5aa167.png" alt="QuickSwitcher" width="45%"></img> <img src="https://i.troplo.com/i/59b63d5aa167.png" alt="QuickSwitcher" width="45%"></img>
<img src="https://i.troplo.com/i/b2d6dd14c6b6.png" alt="QuickSwitcher with AMOLED theme" width="45%"></img> <img src="https://i.troplo.com/i/b2d6dd14c6b6.png" alt="QuickSwitcher with AMOLED theme" width="45%"></img>
## Project setup ## Backend setup
First, configure a database and user (MariaDB strongly recommended) for Colubrina.<br>
Please navigate to the `cli` folder, and run the following commands:
```
yarn
```
to install dependencies, and then
```
node .
```
which should result in an interactive CLI prompt looking like the following:
```
Troplo/Colubrina CLI
Colubrina version 1.0.1
Failed to check for updates, ensure you are connected to the internet, and services.troplo.com is whitelisted behind any potential firewalls.
? Please select an option (Use arrow keys)
Setup
Create user
Run migrations
Update/create config file
Check for updates
Build frontend for production
Exit
```
Select setup, and go through the steps. After completing the initial setup, you may run `yarn build` in the frontend folder, or select "Build frontend for production" in the CLI.<br>
The CLI will populate the database with some default data which are essential for operation.<br>
The backend service can now be started with `node .` in the `backend` folder which will run on port `23998`.
A systemd service example config file can be found at `colubrina.service`
## Frontend setup
Rename .env.example to .env and fill it out with your own information. Rename .env.example to .env and fill it out with your own information.

View file

@ -1,6 +1,6 @@
# Colubrina Backend # Colubrina Backend
Setup instructions can be found in the root README.md document.
## Setup instructions ## Manual Setup instructions (not using Colubrina CLI)
- Run `yarn install` to install the dependencies. - Run `yarn install` to install the dependencies.
- Configure the MariaDB database connection in `config/config.json` using the - Configure the MariaDB database connection in `config/config.json` using the
@ -11,4 +11,4 @@
- Run `yarn serve` to start the proxy with nodemon which automatically restarts - Run `yarn serve` to start the proxy with nodemon which automatically restarts
on file-change for development. on file-change for development.
- Run `yarn start` or `node .` to start the proxy in production. - Run `yarn start` or `node .` to start the proxy in production.
- Colubrina Proxy runs on port 23998. - Colubrina Backend runs on port 23998.

View file

@ -27,7 +27,7 @@ app.get("/api/v1/state", async (req, res) => {
loading: true, loading: true,
notification: process.env.NOTIFICATION, notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE, notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../package.json").version, latestVersion: require("../frontend/package.json").version,
name: process.env.SITE_NAME, name: process.env.SITE_NAME,
allowRegistrations: JSON.parse(process.env.ALLOW_REGISTRATIONS) allowRegistrations: JSON.parse(process.env.ALLOW_REGISTRATIONS)
}) })

View file

@ -22,7 +22,7 @@ module.exports = {
release: process.env.RELEASE, release: process.env.RELEASE,
notification: process.env.NOTIFICATION, notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE, notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../package.json").version latestVersion: require("../../frontend/package.json").version
}) })
const friends = await Friend.findAll({ const friends = await Friend.findAll({
where: { where: {
@ -114,7 +114,7 @@ module.exports = {
release: process.env.RELEASE, release: process.env.RELEASE,
notification: process.env.NOTIFICATION, notification: process.env.NOTIFICATION,
notificationType: process.env.NOTIFICATION_TYPE, notificationType: process.env.NOTIFICATION_TYPE,
latestVersion: require("../../package.json").version latestVersion: require("../../frontend/package.json").version
}) })
socket.emit("unauthorized", { socket.emit("unauthorized", {
message: "Please reauth." message: "Please reauth."

View file

@ -14,6 +14,7 @@
"constantinople": "3.1.1", "constantinople": "3.1.1",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"crypto-random-string": "3.3.1", "crypto-random-string": "3.3.1",
"dayjs": "^1.11.4",
"debug": "~2.6.9", "debug": "~2.6.9",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.17.1", "express": "^4.17.1",

View file

@ -229,7 +229,7 @@ router.put("/state", auth, async (req, res, next) => {
io.emit("siteState", { io.emit("siteState", {
notification: req.body.notification, notification: req.body.notification,
notificationType: req.body.notificationType, notificationType: req.body.notificationType,
latestVersion: require("../../package.json").version, latestVersion: require("../../frontend/package.json").version,
allowRegistrations: req.body.allowRegistrations allowRegistrations: req.body.allowRegistrations
}) })
res.sendStatus(204) res.sendStatus(204)

View file

@ -1023,6 +1023,11 @@ dashdash@^1.12.0:
dependencies: dependencies:
assert-plus "^1.0.0" assert-plus "^1.0.0"
dayjs@^1.11.4:
version "1.11.4"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.4.tgz#3b3c10ca378140d8917e06ebc13a4922af4f433e"
integrity sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==
debug@2.6.9, debug@~2.6.9: debug@2.6.9, debug@~2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"

82
cli/.gitignore vendored Normal file
View file

@ -0,0 +1,82 @@
<<<<<<< HEAD
# Logs
logs
*.log
npm-debug.log*
# 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
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# temp directory
tmp
=======
.DS_Store
node_modules/
dist/
yarn-debug.log*
yarn-error.log*
# Config folder
config/config.json
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
>>>>>>> frontend
frontend/dist1/
rendering/global
rendering/global/5c2f8896a2b7169d4e253944f3b733e2.png
rendering/global/81df1ac4d303319a79db19f45c5c475c.png
rendering/global/d24b3e361c4fc6263e0276f52ada9694.png
rendering/marketplacecontent
rendering/usercontent
nodeinfo.json
.env
usercontent

View file

@ -4,13 +4,13 @@ const path = require("path")
const { Umzug, SequelizeStorage } = require("umzug") const { Umzug, SequelizeStorage } = require("umzug")
const { Sequelize } = require("sequelize") const { Sequelize } = require("sequelize")
const argon2 = require("argon2") const argon2 = require("argon2")
const { production: config } = require("../config/config.json") const { User } = require("../backend/models")
const { User } = require("../models")
const axios = require("axios") const axios = require("axios")
const os = require("os") const os = require("os")
const { execSync } = require('child_process');
console.log("Troplo/Colubrina CLI") console.log("Troplo/Colubrina CLI")
console.log("Colubrina version", require("../../package.json").version) console.log("Colubrina version", require("../frontend/package.json").version)
async function checkForUpdates() { async function checkForUpdates() {
await axios await axios
.get("https://services.troplo.com/api/v1/state", { .get("https://services.troplo.com/api/v1/state", {
@ -20,7 +20,7 @@ async function checkForUpdates() {
timeout: 800 timeout: 800
}) })
.then((res) => { .then((res) => {
if (require("../../package.json").version !== res.data.latestVersion) { if (require("frontend/package.json").version !== res.data.latestVersion) {
console.log("A new version of Colubrina is available!") console.log("A new version of Colubrina is available!")
console.log("Latest version:", res.data.latestVersion) console.log("Latest version:", res.data.latestVersion)
} else { } else {
@ -40,7 +40,8 @@ let state = {
username: "colubrina", username: "colubrina",
password: null, password: null,
database: "colubrina", database: "colubrina",
storage: "./storage.db" storage: "../backend/storage.db",
dialect: "mariadb"
}, },
dbConfig: {} dbConfig: {}
} }
@ -127,18 +128,18 @@ async function testDB() {
async function dbSetup() { async function dbSetup() {
await doSetupDB() await doSetupDB()
fs.writeFileSync( fs.writeFileSync(
path.join(__dirname, "../config/config.json"), path.join(__dirname, "../backend/config/config.json"),
JSON.stringify(state.dbConfig) JSON.stringify(state.dbConfig)
) )
console.log("config/config.json overwritten") console.log("config/config.json overwritten")
} }
async function runMigrations() { async function runMigrations() {
console.log("Running migrations") console.log("Running migrations")
const config = require("../config/config.json").production const config = require("../backend/config/config.json").production
const sequelize = new Sequelize(config) const sequelize = new Sequelize(config)
const umzug = new Umzug({ const umzug = new Umzug({
migrations: { glob: "../migrations/*.js" }, migrations: { glob: "../backend/migrations/*.js" },
context: sequelize.getQueryInterface(), context: sequelize.getQueryInterface(),
storage: new SequelizeStorage({ sequelize }), storage: new SequelizeStorage({ sequelize }),
logger: console, logger: console,
@ -170,7 +171,7 @@ async function createUser() {
} }
async function configureDotEnv() { async function configureDotEnv() {
function setEnvValue(key, value) { function setEnvValue(key, value) {
const ENV_VARS = fs.readFileSync("../.env", "utf8").split(os.EOL) const ENV_VARS = fs.readFileSync("../backend/.env", "utf8").split(os.EOL)
// find the env we want based on the key // find the env we want based on the key
const target = ENV_VARS.indexOf( const target = ENV_VARS.indexOf(
@ -197,8 +198,8 @@ async function configureDotEnv() {
// write everything back to the file system // write everything back to the file system
fs.writeFileSync("../.env", ENV_VARS.join(os.EOL)) fs.writeFileSync("../.env", ENV_VARS.join(os.EOL))
} }
if (!fs.existsSync("../.env")) { if (!fs.existsSync("../backend/.env")) {
fs.writeFileSync("../.env", "") fs.writeFileSync("../backend/.env", "")
} }
setEnvValue( setEnvValue(
"HOSTNAME", "HOSTNAME",
@ -236,10 +237,19 @@ async function init() {
"Run migrations", "Run migrations",
"Update/create config file", "Update/create config file",
"Check for updates", "Check for updates",
"Build frontend for production",
"Exit" "Exit"
]) ])
if (option === "Setup") { if (option === "Setup") {
// run yarn install in ../backend
console.log("Running yarn install")
execSync("cd ../backend && yarn install --frozen-lockfile", () => {
console.log("yarn install complete (backend)")
})
execSync("cd ../frontend && yarn install --frozen-lockfile", () => {
console.log("yarn install complete (frontend)")
})
if (fs.existsSync(path.join(__dirname, "../.env"))) { if (fs.existsSync(path.join(__dirname, "../.env"))) {
const option = await input.confirm(".env already exists, overwrite?", { const option = await input.confirm(".env already exists, overwrite?", {
default: false default: false
@ -250,7 +260,7 @@ async function init() {
} else { } else {
await configureDotEnv() await configureDotEnv()
} }
if (fs.existsSync(path.join(__dirname, "../config/config.json"))) { if (fs.existsSync(path.join(__dirname, "../backend/config/config.json"))) {
const option = await input.select( const option = await input.select(
`config/config.json already exists. Do you want to overwrite it?`, `config/config.json already exists. Do you want to overwrite it?`,
["Yes", "No"] ["Yes", "No"]
@ -262,7 +272,7 @@ async function init() {
await dbSetup() await dbSetup()
} }
await runMigrations() await runMigrations()
const { User, Theme } = require("../models") const { User, Theme } = require("../backend/models")
try { try {
await Theme.bulkCreate( await Theme.bulkCreate(
JSON.parse( JSON.parse(
@ -313,6 +323,11 @@ async function init() {
await runMigrations() await runMigrations()
} else if (option === "Check for updates") { } else if (option === "Check for updates") {
await checkForUpdates() await checkForUpdates()
} else if(option === "Build frontend for production") {
console.log("Building...")
execSync("cd ../frontend && yarn install --frozen-lockfile && yarn build", () => {
console.log("yarn build complete")
})
} else if (option === "Exit") { } else if (option === "Exit") {
process.exit(0) process.exit(0)
} }

14
cli/package.json Normal file
View file

@ -0,0 +1,14 @@
{
"name" : "colubrina-cli",
"private": true,
"dependencies": {
"argon2": "^0.28.7",
"axios": "^0.27.2",
"input": "^1.0.1",
"mariadb": "^3.0.1",
"pg": "^8.7.3",
"sequelize": "^6.21.3",
"sqlite3": "^5.0.10",
"umzug": "^3.1.1"
}
}

1332
cli/yarn.lock Normal file

File diff suppressed because it is too large Load diff

17
colubrina.service Normal file
View file

@ -0,0 +1,17 @@
[Unit]
Description=Colubrina
After=syslog.target
After=network.target
[Service]
RestartSec=2s
Type=simple
User=colubrina
Group=colubrina
WorkingDirectory=/home/colubrina/colubrina/backend
ExecStart=node /home/colubrina/colubrina/backend/index.js
Restart=always
EnvironmentFile=/home/colubrina/colubrina/backend/.env
[Install]
WantedBy=multi-user.target

3
frontend/.env Normal file
View file

@ -0,0 +1,3 @@
VUE_APP_VERSION=[AIV]{version}[/AIV]
VUE_APP_BUILD_DATE=[AIV]{date}[/AIV]
VUE_APP_BASE_URL=

3
frontend/.env.example Normal file
View file

@ -0,0 +1,3 @@
VUE_APP_VERSION=[AIV]{version}[/AIV]
VUE_APP_BUILD_DATE=[AIV]{date}[/AIV]
VUE_APP_BASE_URL=

23
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.sentryclirc
backend/node_modules
backend/config/config.json
backend/.env

View file

@ -2,10 +2,11 @@
"name": "colubrina-chat", "name": "colubrina-chat",
"version": "1.0.1", "version": "1.0.1",
"private": true, "private": true,
"author": "Troplo <troplo@troplo.com>",
"license": "GPL-3.0", "license": "GPL-3.0",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build --no-clean", "build": "vue-cli-service build",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"postinstall": "patch-package" "postinstall": "patch-package"
}, },

View file

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View file

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

Before

Width:  |  Height:  |  Size: 555 B

After

Width:  |  Height:  |  Size: 555 B

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 262 KiB

View file

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

Before

Width:  |  Height:  |  Size: 539 B

After

Width:  |  Height:  |  Size: 539 B

View file

@ -1,7 +1,6 @@
const WebpackAutoInject = require("webpack-auto-inject-version-next") const WebpackAutoInject = require("webpack-auto-inject-version-next")
const Dotenv = require("dotenv-webpack") const Dotenv = require("dotenv-webpack")
//const SentryPlugin = require("@sentry/webpack-plugin") //const SentryPlugin = require("@sentry/webpack-plugin")
const version = require("./package.json").version
let plugins let plugins
if (process.env.NODE_ENV === "production") { if (process.env.NODE_ENV === "production") {
@ -91,10 +90,6 @@ module.exports = {
}, },
productionSourceMap: true, productionSourceMap: true,
configureWebpack: { configureWebpack: {
output: {
filename: `[name].[hash].${version}.js`,
chunkFilename: `[name].[hash].${version}.js`
},
plugins plugins
}, },
pwa: { pwa: {