Browse Source

initial commit

master
RinRi 3 years ago
commit
9024c98490
13 changed files with 2708 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +48
    -0
      models/article.js
  3. +2365
    -0
      package-lock.json
  4. +25
    -0
      package.json
  5. +44
    -0
      routes/articles.js
  6. +36
    -0
      server.js
  7. +67
    -0
      views/apps.ejs
  8. +22
    -0
      views/articles/_form.ejs
  9. +17
    -0
      views/articles/index.ejs
  10. +7
    -0
      views/articles/new.ejs
  11. +14
    -0
      views/articles/show.ejs
  12. +28
    -0
      views/foot.ejs
  13. +33
    -0
      views/head.ejs

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
node_modules
config.js

+ 48
- 0
models/article.js View File

@@ -0,0 +1,48 @@
const mongoose = require("mongoose");
const slugify = require("slugify");
const marked = require("marked");
const createDomPurify = require("dompurify");
const { JSDOM } = require("jsdom");
const dompurify = createDomPurify(new JSDOM().window);

const articleSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
markdown: {
type: String,
required: true,
},
createdAt: {
type: Date,
default: Date.now,
},
slug: {
type: String,
required: true,
unique: true,
},
sanitizedHtml: {
type: String,
required: true,
},
});

articleSchema.pre("validate", function (next) {
if (this.title) {
this.slug = slugify(this.title, { lower: true, strict: true });
}

if (this.markdown) {
this.sanitizedHtml = dompurify.sanitize(marked(this.markdown));
}

next();
});

module.exports = mongoose.model("Article", articleSchema);

+ 2365
- 0
package-lock.json
File diff suppressed because it is too large
View File


+ 25
- 0
package.json View File

@@ -0,0 +1,25 @@
{
"name": "blog",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"devStart": "nodemon server.js"
},
"author": "RinRi-D",
"license": "ISC",
"dependencies": {
"dompurify": "^2.0.11",
"ejs": "^3.1.3",
"express": "^4.17.1",
"highlight.js": "^10.1.1",
"jsdom": "^16.2.2",
"marked": "^1.1.0",
"method-override": "^3.0.0",
"mongoose": "^5.9.19",
"slugify": "^1.4.0"
},
"devDependencies": {
"nodemon": "^2.0.4"
}
}

+ 44
- 0
routes/articles.js View File

@@ -0,0 +1,44 @@
const express = require("express");
const router = express.Router();
const Article = require("../models/article");
const article = require("../models/article");
const { findByIdAndDelete } = require("../models/article");
const config = require("../config");
pass = config.users.pass;

router.get("/new", (req, res) => {
res.render("articles/new", { article: new Article() });
});

router.get("/:slug", async (req, res) => {
const article = await Article.findOne({ slug: req.params.slug });
if (article == null) res.redirect("/");
res.render("articles/show", { article: article });
});

router.post("/", async (req, res) => {
let article = new Article({
title: req.body.title,
description: req.body.description,
markdown: req.body.markdown,
});
if (req.body.password == pass) {
try {
article = await article.save();
res.redirect("/articles/" + article.slug);
} catch (e) {
console.log(e);
res.render("articles/new", { article: article });
}
}
else {
res.render("articles/new", { article: article });
}
});

router.delete("/:id", async (req, res) => {
await Article.findByIdAndDelete(req.params.id);
res.redirect("/");
});

module.exports = router;

+ 36
- 0
server.js View File

@@ -0,0 +1,36 @@
const express = require("express");
const mongoose = require("mongoose");
const Article = require("./models/article");
const articleRouter = require("./routes/articles");
const methodOverride = require("method-override");
const app = express();
const config = require("./config");

db = config.database;
mongoose.connect(
db.url,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
}
);

app.set("view engine", "ejs");

app.use(express.urlencoded({ extended: false }));
app.use("/blog/articles", articleRouter);
app.use(methodOverride("_method"));

app.get("/blog", async (req, res) => {
const articles = await Article.find().sort({ createdAt: "desc" });
res.render("articles/index", { articles: articles });
});

app.get("/", (req, res) => {
res.render("apps");
});

app.use("/", express.static(__dirname + "/public"));

app.listen(5000);

+ 67
- 0
views/apps.ejs View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://rinri-d.xyz/css/bootstrap.min.css">
<link rel="stylesheet" href="https://rinri-d.xyz/css/style.css">
<link rel="shortcut icon" href="https://rinri-d.xyz/images/logo.ico" type="image/x-icon">
<title>RinRi - Apps</title>
</head>

<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<a class="navbar-brand" href="https://rinri-d.xyz" style="color: var(--color1);">
RinRi-D
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link" href="https://rinri-d.xyz">Home</a>
<a class="nav-item nav-link" href="https://blog.rinri-d.xyz">Blog</a>
<a class="nav-item nav-link active" href="https://apps.rinri-d.xyz">Apps</a>
<a class="nav-item nav-link" href="https://git.rinri-d.xyz">Code</a>
<a class="nav-item nav-link" href="https://rinri-d.xyz/rec.html">Recommendations</a>
</div>
</div>
</nav>
<div class="container">
<h1>Node.js apps are hosted on this subdomain.</h1>
<p>
<h2>Apps:</h2>
<ul>
<li><a href="https://blog.rinri-d.xyz">Blog</a></li>
</ul>
</p>
</div>
<footer class="footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Email: <a
href="mailto:rin@rinri-d.xyz">rin@rinri-d.xyz</a> <a
href="https://cloud.rinri-d.xyz/s/TtX6ny2x7XLSaeK">(PGP)</a>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Mastodon: <a
href="https://fosstodon.org/@rinri">@rinri@fosstodon.org</a> </div>
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Matrix: <a
href="https://matrix.to/#/@rinri-d:matrix.org">@rinri-d:matrix.org</a> </div>
</div>
<div style="text-align: center;">&copy; Copyright 2020 RinRi-D.xyz</div>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
</body>

</html>

+ 22
- 0
views/articles/_form.ejs View File

@@ -0,0 +1,22 @@
<div class="form-group">
<label for="title">Title</label>
<input required value="<%= article.title %>" type="text" name="title" id="title" class="form-control">
</div>

<div class="form-group">
<label for="description">Description</label>
<textarea required name="description" id="description" class="form-control"><%= article.description %></textarea>
</div>

<div class="form-group">
<label for="markdown">Markdown</label>
<textarea required name="markdown" id="markdown" class="form-control"><%= article.markdown %></textarea>
</div>

<div class="form-group">
<label for="password">Password</label>
<input required type="password" name="password" id="password" class="form-control">
</div>

<a href="/" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Save</button>

+ 17
- 0
views/articles/index.ejs View File

@@ -0,0 +1,17 @@
<%- include("../head.ejs") %>
<div class="container">
<h1 class="mb-2">Blog</h1>

<% articles.forEach( article => { %>
<div class="item-info mt-4 col-12">
<h3><%= article.title %></h3>
<p class="createdAt text-muted">
<%= article.createdAt.toLocaleString("ru-RU", {dateStyle:"short", timeStyle: "short"}) %> UTC</p>
<p class="description"><%= article.description %></p>
<hr>
<a href="/articles/<%= article.slug %>">Read More</a>
</div>
<% }) %>

</div>
<%- include("../foot.ejs") %>

+ 7
- 0
views/articles/new.ejs View File

@@ -0,0 +1,7 @@
<%- include("../head.ejs") %>
<div class="container">
<form action="/articles" method="POST">
<%- include("_form.ejs") %>
</form>
</div>
<%- include("../foot.ejs") %>

+ 14
- 0
views/articles/show.ejs View File

@@ -0,0 +1,14 @@
<%- include("../head.ejs") %>
<div class="container">
<h1 class="mb-1"><%= article.title %></h1>
<div class="text-muted mb-3">
<%= article.createdAt.toLocaleString("ru-RU", {dateStyle:"short", timeStyle: "short"}) %> UTC
</div>
<div id="out">
<%- article.sanitizedHtml %>
</div>
<script data-isso="https://comments.rinri-d.xyz/" src="https://comments.rinri-d.xyz/js/embed.min.js"></script>

<section id="isso-thread"></section>
</div>
<%- include("../foot.ejs") %>

+ 28
- 0
views/foot.ejs View File

@@ -0,0 +1,28 @@
<footer class="footer">
<div class="container-fluid">
<div class="row">
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Email: <a href="mailto:rin@rinri-d.xyz">rin@rinri-d.xyz</a> <a
href="https://cloud.rinri-d.xyz/s/TtX6ny2x7XLSaeK">(PGP)</a>
</div>
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Mastodon: <a
href="https://fosstodon.org/@rinri">@rinri@fosstodon.org</a> </div>
<div class="col-lg-4 col-md-6 col-sm-12 mb-4">Matrix: <a
href="https://matrix.to/#/@rinri-d:matrix.org">@rinri-d:matrix.org</a> </div>
</div>
<div style="text-align: center;">&copy; Copyright 2020 RinRi-D.xyz</div>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"
integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</body>

</html>

+ 33
- 0
views/head.ejs View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://rinri-d.xyz/css/bootstrap.min.css">
<link rel="stylesheet" href="https://rinri-d.xyz/css/style.css">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/atom-one-dark.min.css">
<link rel="shortcut icon" href="https://rinri-d.xyz/images/logo.ico" type="image/x-icon">
<title>RinRi - Blog</title>
</head>

<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<a class="navbar-brand" href="https://rinri-d.xyz" style="color: var(--color1);">
RinRi-D
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup"
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-item nav-link" href="https://rinri-d.xyz">Home</a>
<a class="nav-item nav-link active" href="https://blog.rinri-d.xyz">Blog</a>
<a class="nav-item nav-link" href="https://apps.rinri-d.xyz">Apps</a>
<a class="nav-item nav-link" href="https://git.rinri-d.xyz">Code</a>
<a class="nav-item nav-link" href="https://rinri-d.xyz/rec.html">Recommendations</a>
</div>
</div>
</nav>

Loading…
Cancel
Save