Compare commits
No commits in common. "b5c164236eb39ffc48674c078d3835f9340ee3be" and "adfc83c78f9049f07223d6d1ab5306e36a061680" have entirely different histories.
b5c164236e
...
adfc83c78f
@ -1,4 +1,4 @@
|
|||||||
name: Build and Push Backend Docker Image
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -9,46 +9,34 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository with submodules
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
token: ${{ secrets.ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Initialize submodules
|
|
||||||
run: |
|
|
||||||
git submodule sync --recursive
|
|
||||||
git submodule update --init --recursive
|
|
||||||
git submodule status
|
|
||||||
ls -la src/modules/db
|
|
||||||
ls -la src/modules/redis
|
|
||||||
|
|
||||||
- name: Login to Registry
|
- name: Login to Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: gitea.spilum.net
|
registry: gitea.spilum.net
|
||||||
username: ${{ secrets.REGISTRY_USER }}
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
- name: Set SHORT_SHA
|
- name: Build Docker Image
|
||||||
id: vars
|
env:
|
||||||
run: echo "short_sha=${GITHUB_SHA::5}" >> "$GITHUB_OUTPUT"
|
BRANCH_NAME: ${{ github.ref_name }}
|
||||||
|
SHORT_HASH: ${{ github.sha }}
|
||||||
|
run: |
|
||||||
|
# Build the image with the commit hash tag
|
||||||
|
docker build --build-arg BUILD_IDENTIFIER=${SHORT_HASH:0:5} -t gitea.spilum.net/spilum.net/mtf-backend:${BRANCH_NAME}-${SHORT_HASH:0:5} .
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
# Tag the same image as "latest"
|
||||||
uses: docker/setup-buildx-action@v3
|
docker tag gitea.spilum.net/spilum.net/mtf-backend:${BRANCH_NAME}-${SHORT_HASH:0:5} gitea.spilum.net/spilum.net/mtf-backend:${BRANCH_NAME}-latest
|
||||||
|
|
||||||
- name: Build and Push Backend (multi-arch)
|
- name: Push Docker Image
|
||||||
uses: docker/build-push-action@v6
|
env:
|
||||||
with:
|
BRANCH_NAME: ${{ github.ref_name }}
|
||||||
context: .
|
SHORT_HASH: ${{ github.sha }}
|
||||||
push: true
|
run: |
|
||||||
platforms: linux/amd64,linux/arm64
|
docker push gitea.spilum.net/spilum.net/mtf-backend:${BRANCH_NAME}-${SHORT_HASH:0:5}
|
||||||
build-args: |
|
docker push gitea.spilum.net/spilum.net/mtf-backend:${BRANCH_NAME}-latest
|
||||||
BUILD_IDENTIFIER=${{ steps.vars.outputs.short_sha }}
|
|
||||||
tags: |
|
|
||||||
gitea.spilum.net/spilum.net/mtf-backend:${{ steps.vars.outputs.short_sha }}
|
|
||||||
gitea.spilum.net/spilum.net/mtf-backend:latest
|
|
||||||
|
|
||||||
- name: Log out from registry
|
- name: Log out from registry
|
||||||
if: always()
|
if: always()
|
||||||
run: docker logout gitea.spilum.net
|
run: docker logout gitea.spilum.net
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,6 +0,0 @@
|
|||||||
[submodule "src/modules/db"]
|
|
||||||
path = src/modules/db
|
|
||||||
url = https://gitea.spilum.net/Spilum.Net/mtf-module-db
|
|
||||||
[submodule "src/modules/redis"]
|
|
||||||
path = src/modules/redis
|
|
||||||
url = https://gitea.spilum.net/Spilum.Net/mtf-module-redis
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "mtf-backend",
|
"name": "spilum-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mtf-backend",
|
"name": "spilum-backend",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
4
src/config/redis.config.js
Normal file
4
src/config/redis.config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
REDIS_HOST: process.env.REDIS_HOST,
|
||||||
|
REDIS_PORT: process.env.REDIS_PORT
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
const {db} = require('../modules/db');
|
const models = require("../models");
|
||||||
|
const db = models.db;
|
||||||
const config = require("../config/auth.config");
|
const config = require("../config/auth.config");
|
||||||
const User = db.user;
|
const User = db.user;
|
||||||
const Role = db.role;
|
const Role = db.role;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const {db} = require('../modules/db');
|
const models = require("../models");
|
||||||
|
const db = models.db;
|
||||||
const {sequelize, Sequelize} = db;
|
const {sequelize, Sequelize} = db;
|
||||||
const {literal} = Sequelize;
|
const {literal} = Sequelize;
|
||||||
const Bill = db.bill;
|
const Bill = db.bill;
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
const {db} = require('../modules/db');
|
const models = require("../models");
|
||||||
|
const db = models.db;
|
||||||
|
const redis = models.redis;
|
||||||
const {sequelize, Sequelize} = db;
|
const {sequelize, Sequelize} = db;
|
||||||
const Intent = db.intent;
|
const Intent = db.intent;
|
||||||
const config = require("../config");
|
const config = require("../config");
|
||||||
|
|
||||||
const {redis} = require('../modules/redis');
|
|
||||||
|
|
||||||
// Get all intents
|
// Get all intents
|
||||||
exports.getAllIntents = async (req, res) => {
|
exports.getAllIntents = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -18,21 +18,7 @@ exports.getAllIntents = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get intent by name
|
|
||||||
exports.getIntentByName = async (req, res) => {
|
|
||||||
try {
|
|
||||||
const intent = await Intent.findOne({where: {name: req.params.name}});
|
|
||||||
|
|
||||||
return res.status(200).send(intent);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("getIntentByName error:", err);
|
|
||||||
|
|
||||||
return res.status(500).send({message: err.message || "Internal server error."});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get intent command (for now by description)
|
// Get intent command (for now by description)
|
||||||
// [UNUSED FOR NOW] Doesn't work with the current intent model
|
|
||||||
exports.getIntentCommand = async (req, res) => {
|
exports.getIntentCommand = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {text, device_id} = req.body;
|
const {text, device_id} = req.body;
|
||||||
@ -45,6 +31,8 @@ exports.getIntentCommand = async (req, res) => {
|
|||||||
|
|
||||||
console.log(`\n[Backend Intents] Processing intent.`);
|
console.log(`\n[Backend Intents] Processing intent.`);
|
||||||
|
|
||||||
|
await redis.client.connect().catch(console.error)
|
||||||
|
|
||||||
const intent = await Intent.findOne({ where: { description: req.body.text } });
|
const intent = await Intent.findOne({ where: { description: req.body.text } });
|
||||||
|
|
||||||
if (intent) {
|
if (intent) {
|
||||||
@ -59,6 +47,8 @@ exports.getIntentCommand = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await redis.client.quit();
|
||||||
|
|
||||||
console.log(`Intent matched and published to stream: ${commandId}`);
|
console.log(`Intent matched and published to stream: ${commandId}`);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
@ -78,6 +68,8 @@ exports.getIntentCommand = async (req, res) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await redis.client.quit();
|
||||||
|
|
||||||
console.log(`No intent match, error published: ${errorId}`);
|
console.log(`No intent match, error published: ${errorId}`);
|
||||||
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@ -89,6 +81,8 @@ exports.getIntentCommand = async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
await redis.client.quit();
|
||||||
|
|
||||||
console.error("getIntentCommand error:", err);
|
console.error("getIntentCommand error:", err);
|
||||||
|
|
||||||
return res.status(500).send({message: err.message || "Internal server error."});
|
return res.status(500).send({message: err.message || "Internal server error."});
|
||||||
@ -98,22 +92,38 @@ exports.getIntentCommand = async (req, res) => {
|
|||||||
// Create intent
|
// Create intent
|
||||||
exports.createIntent = async (req, res) => {
|
exports.createIntent = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const {name, examples} = req.body;
|
const {command, description, type, device_id} = req.body;
|
||||||
|
|
||||||
console.log(`\n[Backend Intents] Creating intent.`)
|
console.log(`\n[Backend Intents] Creating intent.`)
|
||||||
|
|
||||||
await redis.client.connect().catch(console.error);
|
await redis.client.connect().catch(console.error);
|
||||||
|
|
||||||
if (!name || !examples) {
|
if (!command || !description || !type || !device_id) {
|
||||||
return res.status(400).send({message: "Missing required fields: name, examples"});
|
return res.status(400).send({message: "Missing required fields: command, description, type, device_id"});
|
||||||
}
|
}
|
||||||
|
|
||||||
const intent = await Intent.create({
|
const intent = await Intent.create({
|
||||||
name,
|
command,
|
||||||
examples
|
description,
|
||||||
|
type
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(201).send({message: "Intent created successfully.", intent});
|
await redis.client.connect().catch(console.error);
|
||||||
|
|
||||||
|
const commandId = await redis.client.xAdd(
|
||||||
|
'communication',
|
||||||
|
'*',
|
||||||
|
{
|
||||||
|
device_id: device_id,
|
||||||
|
command: "CREATE_INTENT",
|
||||||
|
processed_at: new Date().toISOString(),
|
||||||
|
source: config.DEVICE_ID,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await redis.client.quit();
|
||||||
|
|
||||||
|
return res.status(201).send({message: "Intent created successfully.", intent, commandId});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("createIntent error:", err);
|
console.error("createIntent error:", err);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
const config = require("../config/auth.config");
|
const config = require("../config/auth.config");
|
||||||
const {db} = require('../modules/db');
|
const db = require("../models");
|
||||||
const User = db.user;
|
const User = db.user;
|
||||||
|
|
||||||
verifyToken = (req, res, next) => {
|
verifyToken = (req, res, next) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
const {db} = require('../modules/db');
|
const db = require('../models');
|
||||||
const ROLES = db.ROLES;
|
const ROLES = db.ROLES;
|
||||||
const User = db.user;
|
const User = db.user;
|
||||||
|
|
||||||
|
|||||||
36
src/models/bill.model.js
Normal file
36
src/models/bill.model.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const Bill = sequelize.define("bills", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
amount: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
due: {
|
||||||
|
type: DataTypes.DATEONLY,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: "bills",
|
||||||
|
timestamps: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return Bill;
|
||||||
|
};
|
||||||
72
src/models/index.js
Normal file
72
src/models/index.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
const config = require("../config");
|
||||||
|
const dbConfig = require("../config/db.config");
|
||||||
|
const redisConfig = require("../config/redis.config");
|
||||||
|
|
||||||
|
const Sequelize = require("sequelize");
|
||||||
|
const sequelize = new Sequelize(
|
||||||
|
dbConfig.DB,
|
||||||
|
dbConfig.USER,
|
||||||
|
dbConfig.PASSWORD,
|
||||||
|
{
|
||||||
|
host: dbConfig.HOST,
|
||||||
|
dialect: dbConfig.dialect,
|
||||||
|
port: dbConfig.PORT,
|
||||||
|
pool: {
|
||||||
|
max: dbConfig.pool.max,
|
||||||
|
min: dbConfig.pool.min,
|
||||||
|
acquire: dbConfig.pool.acquire,
|
||||||
|
idle: dbConfig.pool.idle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// SQL Connection
|
||||||
|
const db = {};
|
||||||
|
|
||||||
|
db.Sequelize = Sequelize;
|
||||||
|
db.sequelize = sequelize;
|
||||||
|
|
||||||
|
db.user = require('../models/user.model')(sequelize, Sequelize);
|
||||||
|
db.role = require('../models/role.model')(sequelize, Sequelize);
|
||||||
|
db.bill = require('../models/bill.model')(sequelize, Sequelize);
|
||||||
|
db.intent = require('../models/intent.model')(sequelize, Sequelize);
|
||||||
|
|
||||||
|
db.role.belongsToMany(db.user, {
|
||||||
|
through: "user_roles"
|
||||||
|
});
|
||||||
|
|
||||||
|
db.user.belongsToMany(db.role, {
|
||||||
|
through: "user_roles"
|
||||||
|
});
|
||||||
|
|
||||||
|
db.ROLES = ["user", "admin"];
|
||||||
|
|
||||||
|
// Redis Connection
|
||||||
|
const Redis = require('redis');
|
||||||
|
const redis = {};
|
||||||
|
|
||||||
|
redis.client = Redis.createClient({
|
||||||
|
url: `redis://default@${redisConfig.REDIS_HOST}:${redisConfig.REDIS_PORT}`
|
||||||
|
});
|
||||||
|
|
||||||
|
redis.ensureGroup = async (stream, group) => {
|
||||||
|
await redis.client.connect().catch(console.error);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await redis.client.xGroupCreate(stream, group, "0", {MKSTREAM: true});
|
||||||
|
|
||||||
|
console.log(`Created consumer group: ${group} (${stream})`);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message.includes('BUSYGROUP')) {
|
||||||
|
console.log(`Consumer group already exists: ${group} (${stream})`);
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await redis.client.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
db,
|
||||||
|
redis
|
||||||
|
};
|
||||||
23
src/models/intent.model.js
Normal file
23
src/models/intent.model.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const Intent = sequelize.define("intents", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: "intents",
|
||||||
|
timestamps: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return Intent;
|
||||||
|
};
|
||||||
19
src/models/role.model.js
Normal file
19
src/models/role.model.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const Role = sequelize.define("roles", {
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: "roles",
|
||||||
|
timestamps: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return Role;
|
||||||
|
}
|
||||||
15
src/models/user.model.js
Normal file
15
src/models/user.model.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
module.exports = (sequelize, Sequelize) => {
|
||||||
|
const User = sequelize.define("users", {
|
||||||
|
username: {
|
||||||
|
type: Sequelize.STRING
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: Sequelize.STRING
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: Sequelize.STRING
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return User;
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
Subproject commit 94a4550d0fb1b6e91dcbf8af78bb701720ad9bc4
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Subproject commit e41a75e7436a059ab963c5db7b62ca524cacc4e5
|
|
||||||
@ -15,14 +15,10 @@ const controller = require("../controllers/intent.controller");
|
|||||||
// Get all intents
|
// Get all intents
|
||||||
router.get("/", /*[authJwt.verifyToken, authJwt.isAdmin],*/ controller.getAllIntents);
|
router.get("/", /*[authJwt.verifyToken, authJwt.isAdmin],*/ controller.getAllIntents);
|
||||||
|
|
||||||
// Get intent by name
|
// Get intent command (for now by description)
|
||||||
router.get("/name/:name", controller.getIntentByName);
|
router.post("/getintentcommand", controller.getIntentCommand);
|
||||||
|
|
||||||
// Get intent command by description
|
|
||||||
// [UNUSED FOR NOW]
|
|
||||||
//router.post("/getintentcommand", controller.getIntentCommand);
|
|
||||||
|
|
||||||
// Create intent
|
// Create intent
|
||||||
router.post("/", controller.createIntent);
|
router.post("/createintent", controller.createIntent);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@ -8,19 +8,17 @@ const cors = require("cors");
|
|||||||
const helmet = require("helmet");
|
const helmet = require("helmet");
|
||||||
const rateLimit = require("express-rate-limit");
|
const rateLimit = require("express-rate-limit");
|
||||||
const morgan = require("morgan");
|
const morgan = require("morgan");
|
||||||
|
const models = require('./models');
|
||||||
const {db} = require('./modules/db');
|
const db = models.db;
|
||||||
|
const redis = models.redis;
|
||||||
const {sequelize, Sequelize} = db;
|
const {sequelize, Sequelize} = db;
|
||||||
const Role = db.role;
|
const Role = db.role;
|
||||||
|
|
||||||
const {redis} = require('./modules/redis');
|
|
||||||
|
|
||||||
const authConfig = require("./config/auth.config");
|
const authConfig = require("./config/auth.config");
|
||||||
const config = require("./config");
|
const config = require("./config");
|
||||||
|
|
||||||
const createApp = async () => {
|
const createApp = async () => {
|
||||||
await redis.connect();
|
await redis.ensureGroup("commands", `${config.DEVICE_ID}-cg`);
|
||||||
await redis.ensureGroup("commands", `${config.DEVICE_ID}`);
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@ -60,7 +58,7 @@ const createApp = async () => {
|
|||||||
app.use("/auth", require("./routes/auth.routes"));
|
app.use("/auth", require("./routes/auth.routes"));
|
||||||
app.use("/user", require("./routes/user.routes"));
|
app.use("/user", require("./routes/user.routes"));
|
||||||
app.use("/bills", require("./routes/bill.routes"));
|
app.use("/bills", require("./routes/bill.routes"));
|
||||||
app.use("/intent", require("./routes/intent.routes"));
|
app.use("/fallback/intent", require("./routes/intent.routes"));
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use((req, res) => res.status(404).send({message: "Not Found"}));
|
app.use((req, res) => res.status(404).send({message: "Not Found"}));
|
||||||
@ -97,7 +95,7 @@ const start = async () => {
|
|||||||
|
|
||||||
await initRoles();
|
await initRoles();
|
||||||
|
|
||||||
const app = await createApp();
|
const app = createApp();
|
||||||
|
|
||||||
// Set port and listen
|
// Set port and listen
|
||||||
const PORT = parseInt(config.PORT, 10) || 5000;
|
const PORT = parseInt(config.PORT, 10) || 5000;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user