Added the Sheepstar API to the Archive

This commit is contained in:
Mathias Wagner 2022-09-06 16:37:56 +02:00
parent d6c272cdaf
commit bb78376745
36 changed files with 5346 additions and 0 deletions

View File

@ -0,0 +1,17 @@
name: Deploy to SSH
on:
push:
branches: [ master ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy NodeJS app
uses: garygrossgarten/github-action-ssh@release
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
privateKey: ${{ secrets.REMOTE_SSH_KEY }}
command: "cd /var/www/api/ && git reset --hard && git pull origin master && npm i && cd /var/www/ && npm install && pm2 restart api"

View File

@ -0,0 +1,21 @@
name: Node.js test
on:
push:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 15.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present

4
SheepstarAPIV1/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Project exclude paths
/node_modules/
/.env
/.idea

47
SheepstarAPIV1/README.md Normal file
View File

@ -0,0 +1,47 @@
<h1 align="center">Sheepstar API</h1>
## About The Project
This is the official Sheepstar API. It was written with javascript and made to run with express. Have fun during programming :p
## Getting Started
### Prerequisites
Before you start you need to download npm
```sh
npm install npm@latest -g
```
### Installation
1. Clone the repo
```sh
git clone https://github.com/sheepstar-dc/SheepstarAPI.git
```
2. Install NPM packages as dev
```sh
npm install --save-dev
```
3. Create a `.dotenv` file and replace it to your needs
```dotenv
# DATABASE
MYSQL_HOSTNAME=localhost
MYSQL_USERNAME=username
MYSQL_PASSWORD=password
MYSQL_DATABASE=database
# EXPRESS
SERVER_PORT=3000
# CDN
CDN_MEDIA_PATH=/home/user/uploads
# BOT
CLIENT_SECRET=discord_oauth_client_secret
CLIENT_ID=discord_client_id
REDIRECT_URI=discord_oauth_redirect_uri
```
4. Start the dev server
```sh
npm dev
```
### Contributing
You can contribute by creating a pull request. We'll check it and merge it then

38
SheepstarAPIV1/app.js Normal file
View File

@ -0,0 +1,38 @@
require('dotenv').config();
require("./lib/api");
const express = require('express');
const fileUpload = require('express-fileupload');
const bodyParser = require('body-parser');
const database = require('./config/database');
const app = express();
const cors = require('cors');
database.authenticate()
.then(() => console.log("Connected to " + process.env.MYSQL_HOSTNAME + " with user " + process.env.MYSQL_USERNAME))
.catch(err => console.log("An error occurred while connecting to database: " + err));
app.use(cors());
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());
app.use(fileUpload({createParentPath: true}));
// Routes that doesn't need authentication
app.use("/auth", require("./routes/auth"));
// At this point, we have a valid token, so we can add the protected routes
app.use(require("./middlewares/verifyToken"));
app.use("/user", require("./routes/user"));
app.use("/gift", require("./routes/gift"));
app.use("/apikey", require("./routes/apikey"));
app.use("/media", require("./routes/media"));
app.use("/link", require("./routes/link"));
app.use("/article", require("./routes/article"));
app.use("/shop", require("./routes/shop"));
database.sync();
app.use((req, res) => {
res.status(404).json({message: "Endpoint not found"});
});
app.listen(process.env.SERVER_PORT || 3000);

View File

@ -0,0 +1,13 @@
const { Sequelize } = require("sequelize");
module.exports = new Sequelize(process.env.MYSQL_DATABASE, process.env.MYSQL_USERNAME, process.env.MYSQL_PASSWORD, {
host: process.env.MYSQL_HOSTNAME,
dialect: 'mysql',
logging: false,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
});

View File

@ -0,0 +1,9 @@
global.DISCORD_API_BASE = "https://discord.com/api";
// oAuth2 Endpoints
global.DISCORD_OAUTH2_BASE = DISCORD_API_BASE+"/oauth2";
global.DISCORD_TOKEN_ENDPOINT = DISCORD_OAUTH2_BASE+"/token";
// User Endpoints
global.DISCORD_USER_ENDPOINT = DISCORD_API_BASE+"/users/@me";
global.DISCORD_USER_GUILDS_ENDPOINT = DISCORD_USER_ENDPOINT+"/guilds";

View File

@ -0,0 +1,20 @@
const axios = require('axios');
const qs = require('qs');
const Session = require("../models/Session");
async function refreshToken(token) {
try {
const session = await Session.findOne({where: {token: token}});
const {data} = await axios.post(DISCORD_TOKEN_ENDPOINT, qs.stringify({
client_id: process.env.CLIENT_ID, client_secret: process.env.CLIENT_SECRET, grant_type: "refresh_token",
refresh_token: session.refresh_token
}));
await Session.update({access_token: data.access_token, refresh_token: data.refresh_token}, {where: {token: token}});
return data.access_token;
} catch (e) {
return "could not refresh token";
}
}
module.exports.refreshToken = refreshToken;

View File

@ -0,0 +1,25 @@
const GrantedPermission = require("../models/GrantedPermission");
const Session = require("../models/Session");
module.exports = function(permissionNode) {
return async function(req, res, next) {
// Set the token
let token = req.token;
if (req.tokenType === "client") token = req.clientId;
// Get permissions from client
const clientPermissions = await GrantedPermission.findAll({where: {token: token}});
// Check permission
for (let permission in clientPermissions) {
if (clientPermissions[permission].permissionNode === "*") return next();
try {
if (new RegExp(clientPermissions[permission].permissionNode).test(permissionNode)) return next();
} catch {}
}
// Return if client has no permission
res.status(403).json({message: `You need the permission '${permissionNode}' to perform this action.`});
}
}

View File

@ -0,0 +1,43 @@
const Session = require("../models/Session");
const APIKey = require("../models/APIKey");
const Account = require("../models/Account");
module.exports = async function(req, res, next) {
const authHeader = req.header("Authorization");
if (!authHeader) return res.status(401).json({message: "Missing Authorization header"});
// Check if authentication is a token
if (authHeader.startsWith("Bearer ")) {
req.token = authHeader.substring(7, authHeader.length);
try {
// Check if token is a session
const {count: sessionCount, rows: sessionRows} = await Session.findAndCountAll({where: {token: req.token}});
if (sessionCount === 1) {
req.sessionInfo = sessionRows[0].dataValues;
req.clientId = sessionRows[0].client_id;
req.tokenType = "client";
}
// Check if token is a api key
const {count: tokenCount, rows: tokenRows} = await APIKey.findAndCountAll({where: {token: req.token}});
if (tokenCount === 1) {
req.keyInfo = tokenRows[0].dataValues;
req.clientId = tokenRows[0].client_id;
req.tokenType = "apikey";
}
// Set data if the token is valid
if (req.tokenType) {
req.userInfo = await Account.findOne({where: {client_id: req.clientId}});
return next();
}
res.status(401).json({message: "Invalid token"});
} catch (e) {
res.status(500).json({message: "Unexpected error occurred"});
}
} else res.status(401).json({message: "Authorization required"});
}

View File

@ -0,0 +1,13 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("api_keys", {
token: {
type: Sequelize.STRING,
allowNull: false
},
client_id: {
type: Sequelize.STRING,
allowNull: false
}
});

View File

@ -0,0 +1,25 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("accounts",{
client_id: {
type: Sequelize.STRING,
allowNull: false
},
username: {
type: Sequelize.STRING,
allowNull: false
},
email: {
type: Sequelize.STRING,
allowNull: false
},
locale: {
type: Sequelize.STRING,
defaultValue: "en"
},
avatar: {
type: Sequelize.STRING,
allowNull: false
}
});

View File

@ -0,0 +1,19 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("activated_articles", {
articleID: {
type: Sequelize.STRING,
maxLength: 128,
allowNull: false
},
guildID: {
type: Sequelize.STRING,
maxLength: 128,
allowNull: false
},
expiry_date: {
type: Sequelize.DATE,
allowNull: false
}
})

View File

@ -0,0 +1,37 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("articles", {
moduleName: {
type: Sequelize.STRING,
allowNull: false
},
articleID: {
type: Sequelize.STRING,
allowNull: false
},
articleName: {
type: Sequelize.STRING,
allowNull: false
},
imageID: {
type: Sequelize.STRING,
allowNull: true
},
articleDescription: {
type: Sequelize.STRING,
defaultValue: "Sheepstar article"
},
articleExtras: {
type: Sequelize.JSON,
defaultValue: {}
},
articlePrice: {
type: Sequelize.INTEGER,
defaultValue: 0
},
maxOwnCount: {
type: Sequelize.INTEGER,
defaultValue: 1
}
});

View File

@ -0,0 +1,21 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("gifts", {
giftID: {
type: Sequelize.STRING,
allowNull: false
},
articleID: {
type: Sequelize.STRING,
allowNull: false
},
expiry_date: {
type: Sequelize.DATE,
allowNull: false
},
item_expiry_date: {
type: Sequelize.DATE,
allowNull: false
}
});

View File

@ -0,0 +1,16 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("granted_permissions", {
tokenType: {
type: Sequelize.STRING,
allowNull: false
},
token: {
type: Sequelize.STRING,
allowNull: false
},
permissionNode: {
type: Sequelize.STRING,
allowNull: false
}
});

View File

@ -0,0 +1,21 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("inventorys", {
clientID: {
type: Sequelize.STRING,
allowNull: false
},
articleID: {
type: Sequelize.STRING,
allowNull: false
},
articleExtras: {
type: Sequelize.JSON,
allowNull: true
},
expiry_date: {
type: Sequelize.DATE,
allowNull: false
}
})

View File

@ -0,0 +1,25 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("media", {
assetID: {
type: Sequelize.STRING,
allowNull: false
},
assetCreator: {
type: Sequelize.STRING,
allowNull: false
},
assetEnding: {
type: Sequelize.STRING,
allowNull: false
},
assetName: {
type: Sequelize.STRING,
allowNull: false
},
assetDescription: {
type: Sequelize.STRING,
defaultValue: "Default sheepstar asset"
}
});

View File

@ -0,0 +1,17 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("permissions", {
permissionNode: {
type: Sequelize.STRING,
allowNull: false
},
permissionName: {
type: Sequelize.STRING,
allowNull: false
},
permissionDescription: {
type: Sequelize.STRING,
allowNull: false
}
});

View File

@ -0,0 +1,24 @@
const Sequelize = require('sequelize');
const db = require('../config/database');
module.exports = db.define("sessions", {
token: {
type: Sequelize.STRING,
allowNull: false
},
client_id: {
type: Sequelize.STRING,
allowNull: false
},
access_token: {
type: Sequelize.STRING,
allowNull: false
},
refresh_token: {
type: Sequelize.STRING,
allowNull: false
},
user_agent: {
type: Sequelize.STRING,
defaultValue: "User-Agent not given"
}
});

View File

@ -0,0 +1,37 @@
const Sequelize = require('sequelize');
const db = require("../config/database");
module.exports = db.define("shortened_links",{
shorten_url: {
type: Sequelize.STRING,
allowNull: false
},
original_url: {
type: Sequelize.STRING,
allowNull: false
},
show_meta_data: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
meta_title: {
type: Sequelize.STRING,
defaultValue: "Default URL title",
allowNull: true
},
meta_description: {
type: Sequelize.STRING,
defaultValue: "Default URL description",
allowNull: true
},
meta_image: {
type: Sequelize.STRING,
defaultValue: "Default URL image",
allowNull: true
},
meta_color: {
type: Sequelize.STRING,
defaultValue: "#5865f2",
allowNull: true
}
});

4172
SheepstarAPIV1/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
{
"name": "sheepstarapi",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon app.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^2.0.7"
},
"dependencies": {
"axios": "^0.24.0",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"express-fileupload": "^1.2.1",
"joi": "^17.4.0",
"mysql2": "^2.2.5",
"qs": "^6.7.0",
"sequelize": "^6.6.2"
}
}

View File

@ -0,0 +1,45 @@
const app = require('express').Router();
const crypto = require('crypto');
const checkPermission = require("../middlewares/checkPermission");
const APIKey = require("../models/APIKey");
app.put("/", checkPermission("admin.apikey.create"), async (req, res) => {
// Generate apikey
const token = crypto.randomBytes(64).toString('hex');
try {
// Return response
res.status(200).send(await APIKey.create({token: token, client_id: req.clientId}));
} catch (e) {
res.status(500).json({message: "An internal error occurred."})
}
});
app.get("/:apiKey", checkPermission("admin.apikey.info"), async (req, res) => {
// Validate request
if (!req.params.apiKey) return res.status(400).json({message: "You need to provide an api key"});
// Search for the api key
const apikey = await APIKey.findOne({where: {token: req.params.apiKey}});
if (apikey) {
// Return response
res.status(200).json(apikey);
} else res.status(402).json({message: "Invalid API key"});
});
app.delete("/:apiKey", checkPermission("admin.apikey.delete"), async (req, res) => {
// Validate request
if (!req.params.apiKey) return res.status(400).json({message: "You need to provide an api key"});
// Search for the api key
const apikey = await APIKey.findOne({where: {token: req.params.apiKey}});
if (apikey) {
// Destroy api key
await APIKey.destroy({where: {token: req.params.apiKey}});
// Return response
res.status(200).json({message: "Successfully deleted"});
} else res.status(402).json({message: "Invalid API key"});
});
module.exports = app;

View File

@ -0,0 +1,70 @@
const app = require('express').Router();
const checkPermission = require("../middlewares/checkPermission");
const articleValidation = require("../validation/articleValidation");
const Article = require("../models/Article");
app.put("/", checkPermission("admin.article.create"), async (req, res) => {
// Validate request
const {error} = articleValidation.create.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if article already exists
const {count: articleCount} = await Article.findAndCountAll({where: req.body});
if (articleCount === 1) return res.status(400).json({message: "Article already exists"});
// Create article
res.status(200).json(await Article.create(req.body));
});
app.delete("/", checkPermission("admin.article.delete"), async (req, res) => {
// Validate request
const {error} = articleValidation.info.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if already exists
const {count: articleCount} = await Article.findAndCountAll({where: req.body});
if (articleCount === 0) return res.status(400).json({message: "Article does not exist"});
// Delete the article
await Article.destroy({where: req.body});
res.status(200).json({message: "Article deleted successfully"});
});
app.get("/:articleId", checkPermission("admin.article.info"), async (req, res) => {
// Validate request
if (!req.params.articleId) return res.status(400).json({message: "Missing article id"});
// Get the article information
const info = await Article.findByPk(req.params.articleId);
if (info) {
res.status(200).json(info);
} else res.status(404).json({message: "Article not found"});
});
app.get("/:moduleName/list", checkPermission("articles.list"), async (req, res) => {
// Validate request
if (!req.params.moduleName) return res.status(400).json({message: "Module name is required"});
// Get the article
const list = await Article.findAndCountAll({where: {moduleName: req.params.moduleName}});
if (list) {
res.status(200).json(list.rows);
} else res.status(404).json({message: "No articles found"});
});
app.get("/:moduleName/:articleId", checkPermission("admin.article.info"), async (req, res) => {
// Validate request
if (!req.params.moduleName) return res.status(400).json({message: "Missing module name"});
if (!req.params.articleId) return res.status(400).json({message: "Missing article id"});
// Get the article information
const info = await Article.findOne({where: {moduleName: req.params.moduleName, articleId: req.params.articleId}});
if (info) {
res.status(200).json(info);
} else res.status(404).json({message: "Article not found"});
});
module.exports = app;

View File

@ -0,0 +1,57 @@
const app = require('express').Router();
const qs = require("qs");
const axios = require('axios');
const crypto = require("crypto");
const Account = require("../models/Account");
const Session = require("../models/Session");
app.post("/token", async (req, res) => {
// Check if code is valid
if (!req.body.code) return res.status(400).json({message: "You need to provide a code"});
try {
// Get access & refresh token
const tokens = await axios.post(DISCORD_TOKEN_ENDPOINT, qs.stringify({
"code": req.body.code, "grant_type": "authorization_code", "client_secret": process.env.CLIENT_SECRET,
"client_id": process.env.CLIENT_ID, "redirect_uri": process.env.REDIRECT_URI
}));
// Check if all scopes are provided
if (tokens.data.scope !== "identify email guilds") throw "Not all scopes were specified";
// Get user data
const {data} = await axios.get(DISCORD_USER_ENDPOINT, {
headers: {Authorization: "Bearer " + tokens.data.access_token}
});
//Check if account exists
const account = await Account.findAndCountAll({where: {client_id: data.id}});
// Define user data
userData = {client_id: data.id, username: data.username+"#"+data.discriminator,
email: data.email, locale: data.locale, avatar: data.avatar};
// Create/update account
if (account.count === 1)
await Account.update(userData,{where: {client_id: data.id}});
else await Account.create(userData);
// Generate random token
const token = crypto.randomBytes(48).toString('hex');
// Create session
await Session.create({
token: token, client_id: data.id, access_token: tokens.data.access_token, refresh_token: tokens.data.refresh_token,
user_agent: req.get("user-agent")
});
// Return token
res.status(200).json({token: token});
} catch (e) {
res.status(400).json({message: "Something went wrong"});
}
});
module.exports = app;

View File

@ -0,0 +1,135 @@
const app = require('express').Router();
const crypto = require('crypto');
const checkPermission = require("../middlewares/checkPermission");
const giftValidation = require("../validation/giftValidation");
const Article = require("../models/Article");
const Gift = require("../models/Gift");
const ActivatedArticle = require("../models/ActivatedArticle");
app.put("/", checkPermission("admin.gift.create"), async (req, res) => {
// Validate request
const {error} = giftValidation.create.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Set optional fields
const giftID = (req.body.giftID || crypto.randomBytes(15).toString("hex")).toUpperCase();
const expiry_date = new Date(req.body.expiry_date || new Date("4000-01-01"));
const item_expiry_date = new Date(req.body.item_expiry_date || new Date("4000-01-01"));
// Check if article exists
const {count: articleCount} = await Article.findAndCountAll({where: {id: req.body.articleID}});
if (articleCount === 0) return res.status(400).json({message: "Article does not exist"});
// Check if code already exists
const {count: codeCount} = await Gift.findAndCountAll({where: {giftID: giftID}});
if (codeCount === 1) return res.status(400).json({message: "Code already exists"});
// Create gift
await Gift.create({
giftID: giftID,
articleID: req.body.articleID,
expiry_date: expiry_date,
item_expiry_date: item_expiry_date
});
// Return gift
res.status(200).json({
giftID: giftID, articleID: req.body.articleID,
expiry_date: expiry_date.toISOString(), item_expiry_date: item_expiry_date.toISOString()
});
});
app.get("/:giftID", checkPermission("admin.gift.info"), async (req, res) => {
// Validate request
if (!req.params.giftID) return res.status(400).json({message: "Gift ID is required"});
// Check if code is in database
const giftInfo = await Gift.findOne({where: {giftID: req.params.giftID}});
// Return info
if (giftInfo)
res.status(200).json(giftInfo);
else res.status(404).json({message: "Gift not found"});
});
app.delete("/:giftID", checkPermission("admin.gift.delete"), async (req, res) => {
// Validate request
if (!req.params.giftID) return res.status(400).json({message: "Gift ID is required"});
// Check if code exists
const {count} = await Gift.findAndCountAll({where: {giftID: req.params.giftID}});
if (count === 0) return res.status(400).json({message: "Gift not found"});
// Delete code
await Gift.destroy({where: {giftID: req.params.giftID}});
// Send response
res.status(200).json({message: "Gift successfully deleted"});
});
app.patch("/", checkPermission("admin.gift.update"), async (req, res) => {
// Validate request
const {error} = giftValidation.update.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if code exists
const {count} = await Gift.findAndCountAll({where: {giftID: req.body.giftID}});
if (count === 0) return res.status(400).json({message: "Gift not found"});
// Add changes to object
const updatedGift = {};
if (req.body.articleID) {
// Check if article exists
const {count: articleCount} = await Article.findAndCountAll({where: {articleID: req.body.articleID}});
if (articleCount === 0) return res.status(400).json({message: "Article does not exist"});
updatedGift["articleID"] = req.body.articleID;
}
if (req.body.expiry_date) updatedGift["expiry_date"] = req.body.expiry_date;
if (req.body.item_expiry_date) updatedGift["item_expiry_date"] = req.body.item_expiry_date;
// Update
await Gift.update(updatedGift, {where: {giftID: req.body.giftID}});
// Send response
res.status(200).json({message: "Gift updated successfully"});
});
app.post("/redeem", async (req, res) => {
// Validate request
const {error} = giftValidation.redeem.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if code is in database
const giftInfo = await Gift.findOne({where: {giftID: req.body.giftID}});
// Check if gift exists
if (!giftInfo) return res.status(404).json({message: "Gift not found"});
// Get the article
const article = await Article.findOne({where: {id: giftInfo.articleID}});
// Check if article limit reached
const {count} = await ActivatedArticle.findAndCountAll({
where: {
articleID: giftInfo.articleID,
guildID: req.body.guildID
}
});
if (count >= article.maxOwnCount) return res.status(400).json({message: "Maximum limit of this item reached"});
// Redeem article
await ActivatedArticle.create({
articleID: giftInfo.articleID,
expiry_date: giftInfo.item_expiry_date,
guildID: req.body.guildID
});
await Gift.destroy({where: {giftID: req.body.giftID}});
// Send response
res.json({message: "Article successfully redeemed"});
});
module.exports = app;

View File

@ -0,0 +1,51 @@
const app = require('express').Router();
const crypto = require("crypto");
const checkPermission = require("../middlewares/checkPermission");
const ShortenedLink = require("../models/ShortenedLink");
const linkValidation = require("../validation/linkValidation");
app.put("/", checkPermission("url.create"), async (req, res) => {
// Validate request
const {error} = linkValidation.short.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Set optional fields
req.body.shorten_url = req.body.custom_url || crypto.randomBytes(3).toString('hex');
// Check if shorten url exists
const {count} = await ShortenedLink.findAndCountAll({where: {shorten_url: req.body.shorten_url}});
if (count === 1) return res.status(400).json({message: "URL already exists"});
// Create the link
await ShortenedLink.create(req.body);
// Return the response
res.status(200).json({message: "Link successfully created", "shorten_url": req.body.shorten_url});
});
app.delete("/:code", checkPermission("url.delete"), async (req, res) => {
if (!req.params.code) return res.status(400).json({message: "You need to provide the shorten url code"});
// Search for the link
const link = await ShortenedLink.findOne({where: {shorten_url: req.params.code}});
if (link) {
// Destroy the link
await ShortenedLink.destroy({where: {shorten_url: req.params.code}});
// Return response
res.status(200).json({message: "Link successfully deleted"});
} else res.status(404).json({message: "Link not found"});
});
app.get("/:code", checkPermission("url.info"), async (req, res) => {
if (!req.params.code) return res.status(400).json({message: "You need to provide the shorten url code"});
// Search the link
const link = await ShortenedLink.findOne({where: {shorten_url: req.params.code}});
if (link) {
// Return response
res.status(200).json(link);
} else res.status(404).json({message: "Link not found"});
})
module.exports = app;

View File

@ -0,0 +1,72 @@
const app = require('express').Router();
const fs = require('fs');
const checkPermission = require("../middlewares/checkPermission");
const mediaValidation = require("../validation/mediaValidation");
const crypto = require('crypto');
const Media = require("../models/Media");
app.put("/", checkPermission("admin.media.upload"), async (req, res) => {
// Validate request
const {error} = mediaValidation.upload.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if file was provided
if (req.files && req.files.asset) {
const asset = req.files.asset;
const assetID = crypto.randomBytes(8).toString('hex');
const splittedAsset = asset.name.split(".");
const assetEnding = splittedAsset[splittedAsset.length - 1];
// Check if the asset exists
const {count: assetCount} = await Media.findAndCountAll({where: {assetID: assetID}});
if (assetCount === 1) return res.status(400).json({message: "This asset already exists"});
// Create the asset
await Media.create({
assetID: assetID, assetCreator: req.token, assetEnding: assetEnding,
assetName: asset.name, assetDescription: req.body.assetDescription
});
// Move the asset to the upload directory
await req.files.asset.mv(process.env.CDN_MEDIA_PATH + "/" + assetID);
// Send response
res.status(200).json({
message: "Asset successfully uploaded", id: assetID,
url: "https://cdn.sheepstar.xyz/" + assetID + "." + assetEnding
});
} else res.status(400).json({message: "You must provide an asset"});
});
app.delete("/:assetID", checkPermission("admin.media.delete"), async (req, res) => {
// Validate request
if (!req.params.assetID) return res.status(400).json({message: "You must provide an asset ID"});
// Check if asset exists
const {count} = await Media.findAndCountAll({where: {assetID: req.params.assetID}});
if (count === 0) res.status(404).json({message: "The provided asset does not exist."});
// Delete asset
try {
fs.unlinkSync(process.env.CDN_MEDIA_PATH + "/" + req.params.assetID);
await Media.destroy({where: {assetID: req.params.assetID}});
} catch (e) {
return res.status(500).json({message: "The provided asset could not be deleted"});
}
res.status(200).json({message: "Asset successfully deleted"});
});
app.get("/:assetID", checkPermission("admin.media.info"), async (req, res) => {
// Validate request
if (!req.params.assetID) return res.status(400).json({message: "You must provide an asset ID"});
// Check if asset exists
const {count, rows} = await Media.findAndCountAll({where: {assetID: req.params.assetID}});
if (count === 0) res.status(404).json({message: "The provided asset does not exist."});
// Return response
res.status(200).json(rows);
});
module.exports = app;

View File

@ -0,0 +1,81 @@
const app = require('express').Router();
const checkPermission = require("../middlewares/checkPermission");
const shopValidation = require("../validation/shopValidation");
const Article = require("../models/Article");
const ActivatedArticle = require("../models/ActivatedArticle");
const {owns} = require("../validation/shopValidation");
app.get("/", checkPermission("shop.info"), async (req, res) => {
// Validate request
const {error} = shopValidation.info.validate(req.query);
if (error) return res.status(400).json({message: error.details[0].message});
// Get article info
const info = await ActivatedArticle.findAndCountAll({where: req.query});
// Return info
if (info) {
res.status(200).json(info.rows);
} else res.json(404).json({message: "Article not found"});
});
app.get("/owns", checkPermission("shop.info"), async (req, res) => {
// Validate request
const {error} = shopValidation.owns.validate(req.query);
if (error) return res.status(400).json({message: error.details[0].message});
if (await Article.findByPk(req.query.articleID) === null) return res.status(404).json({message: "Article not found"});
// Get the article information
const info = await ActivatedArticle.findOne({
where: {
articleID: req.query.articleID,
guildID: req.query.guildID
}
});
const premiumInfo = await ActivatedArticle.findOne({
where: {
articleID: 1,
guildID: req.query.guildID
}
});
res.status(200).json(info !== null || premiumInfo !== null);
});
app.delete("/", checkPermission("shop.delete"), async (req, res) => {
// Validate request
const {error} = shopValidation.owns.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if already exists
const {count: articleCount} = await ActivatedArticle.findAndCountAll({where: req.body});
if (articleCount === 0) return res.status(400).json({message: "Article does not exist"});
// Delete the article
await ActivatedArticle.destroy({where: req.body});
res.status(200).json({message: "Article deleted successfully"});
});
app.put("/", checkPermission("shop.buy"), async (req, res) => {
// Validate request
const {error} = shopValidation.buy.validate(req.body);
if (error) return res.status(400).json({message: error.details[0].message});
// Check if article exists
const article = await Article.findByPk(req.body.articleID);
if (!article) return res.status(404).json({message: "Article does not exist"});
if (!req.body.expiry_date) req.body.expiry_date = new Date("4000-01-01");
// Check if article limit reached
const {count} = await ActivatedArticle.findAndCountAll({where: req.body});
if (count >= article.maxOwnCount) return res.status(400).json({message: "Maximum limit of this item reached"});
res.status(200).json(await ActivatedArticle.create(req.body));
});
module.exports = app;

View File

@ -0,0 +1,17 @@
const app = require('express').Router();
const axios = require('axios');
const {refreshToken} = require("../lib/discord");
app.get("/guilds", async (req, res) => {
const accessToken = await refreshToken(req.token);
try {
const {data} = await axios.get(DISCORD_USER_GUILDS_ENDPOINT, {
headers: {Authorization: "Bearer " + accessToken}
});
res.status(200).json(data);
} catch (e) {
res.status(500).json({message: "Something went wrong"});
}
});
module.exports = app;

View File

@ -0,0 +1,25 @@
const Joi = require('joi');
module.exports.create = Joi.object({
moduleName: Joi.string()
.alphanum()
.min(2)
.max(10)
.required(),
articleID: Joi.string()
.required(),
articleName :Joi.string()
.required(),
imageID: Joi.string()
.min(4)
.max(32),
articleDescription: Joi.string(),
articleExtras: Joi.object(),
articlePrice: Joi.number(),
maxOwnCount: Joi.number()
});
module.exports.info = Joi.object({
id: Joi.string()
.required()
});

View File

@ -0,0 +1,36 @@
const Joi = require('joi');
module.exports.create = Joi.object({
giftID: Joi.string()
.alphanum()
.min(3)
.max(40),
articleID: Joi.string()
.required(),
expiry_date: Joi.date()
.min("now"),
item_expiry_date: Joi.date()
.min("now")
});
module.exports.update = Joi.object({
giftID: Joi.string()
.alphanum()
.min(3)
.max(40)
.required(),
articleID: Joi.string(),
expiry_date: Joi.date()
.min("now"),
item_expiry_date: Joi.date()
.min("now")
});
module.exports.redeem = Joi.object({
guildID: Joi.string()
.alphanum()
.required(),
giftID: Joi.string()
.alphanum()
.required()
});

View File

@ -0,0 +1,26 @@
const Joi = require('joi');
module.exports.short = Joi.object({
original_url: Joi.string()
.min(5)
.uri()
.max(250)
.required(),
custom_url: Joi.string()
.alphanum()
.min(2)
.max(15),
show_meta_data: Joi.bool(),
meta_title: Joi.string()
.min(2)
.max(25),
meta_description: Joi.string()
.min(5)
.max(100),
meta_image: Joi.string()
.uri()
.min(5)
.max(255),
meta_color: Joi.string()
.pattern(new RegExp("^#[A-Fa-f0-9]{6}$"))
});

View File

@ -0,0 +1,15 @@
const Joi = require('joi');
module.exports.upload = Joi.object({
assetEnding: Joi.string()
.alphanum()
.min(3)
.max(10),
assetName: Joi.string()
.alphanum()
.min(5)
.max(20),
assetDescription: Joi.string()
.min(5)
.max(250)
});

View File

@ -0,0 +1,25 @@
const Joi = require('joi');
module.exports.info = Joi.object({
articleID: Joi.string(),
guildID: Joi.string()
.alphanum()
.required()
});
module.exports.owns = Joi.object({
articleID: Joi.string()
.required(),
guildID: Joi.string()
.alphanum()
.required()
});
module.exports.buy = Joi.object({
articleID: Joi.string()
.required(),
guildID: Joi.string()
.required(),
expiry_date: Joi.date()
.min("now")
});