// controllers/admin/post.ts
import { getCookies, deleteCookie } from "cookies";
import { setting, secret_key, myredis } from 'setting';
import { verify } from "jwt";
import postdb from "../../models/post.ts";
class Post{
async getPage(req, ctx){
const cookies = getCookies(req.headers);
if((cookies)&&(cookies.session_id)){
const jwt = await myredis.get(cookies.session_id);
try{
const payload = await verify(jwt, secret_key, "HS512");
if(payload.user){
const config = setting();
config.page_title = "ទំព័រការផ្សាយ";
config.username = payload.user.title;
config.count = await postdb.count();
config.items = await postdb.getPosts(config.post_amount);
return await ctx.render({"setting": config});
}
}catch(error){
console.log(error);
const config = setting();
config.page_title = "ទំព័រចុះឈ្មោះ";
const resp = new Response();
deleteCookie(resp.headers, "session_id");
return new Response(undefined, { headers: {location: `/login`}, status: 302 });
}
}
return new Response(undefined, { headers: {location: `/login`}, status: 302 });
}
async createPost(req, ctx){
const cookies = getCookies(req.headers);
if((cookies)&&(cookies.session_id)){
const jwt = await myredis.get(cookies.session_id);
try{
const payload = await verify(jwt, secret_key, "HS512");
if(payload.user.role in {'Admin':1,'Editor':1,'Author':1}){
await postdb.insertPost(req, payload.user.id);
}
return new Response(undefined, { headers: {location: `/admin/post`}, status: 302 });
}catch(error){
console.log(error);
const resp = new Response(undefined, { headers: {location: `/login`}, status: 302 });
deleteCookie(resp.headers, "session_id");
return resp;
}
}
return new Response(undefined, { headers: {location: `/login`}, status: 302 });
}
}
export default new Post();
// models/post.ts
import { mydb } from "setting"
interface PostSchema {
_id: ObjectId;
id: string;
title: string;
content: string;
categories: string[];
thumb: string;
date: string;
videos: string;
userid: string;
}
class Post{
async count(query={}){
const posts = mydb.collection<PostSchema>("posts")
return await posts.countDocuments(query)
}
async insertPost(req, user_id: string){
const id = crypto.randomUUID()
const formData = await req.formData()
let categories: string[]
if(formData.get("categories").includes(',')){
categories = formData.get("categories").split(',')
}else{
categories = [formData.get("categories")]
}
const new_post = {
id: id,
title: formData.get("title"),
content: formData.get("content"),
categories: categories,
thumb: formData.get("thumb"),
date: formData.get("datetime"),
videos: formData.get("videos"),
userid: user_id,
}
const posts = mydb.collection<PostSchema>("posts")
await posts.insertOne(new_post)
}
async getPosts(amount: number, query={}){
const posts = mydb.collection<PostSchema>("posts")
return await posts.find(query).sort({date:-1,_id:-1}).limit(amount).toArray()
}
}
export default new Post()
// components/admin/index.tsx
/** @jsx h */
import { h } from "preact"
import { PageProps } from "$fresh/server.ts";
import Base from "../base.tsx"
function IndexTsx(props: PageProps){
const Page = props.data.pageInner;
const items = props.data.setting.items;
const listItems = items.map((item) =>
<li>
<a class="thumb" href={`/post/${item.id}`}>
<img src={item.thumb} />
{((item.videos !== "" )&&(item.videos !== "[]")) &&
<img class="play-icon" src={`/images/play.png`} />
}
</a>
<div class="title">
<a href={`/post/${item.id}`}>{item.title}</a>
<div>{(new Date(item.date)).toLocaleDateString('it-IT')}</div>
</div>
<div class="edit">
<a href={`/admin/post/edit/${item.id}`}><img src={`/images/edit.png`} /></a>
<a href={`/admin/post/delete/${item.id}`}><img src={`/images/delete.png`} /></a>
</div>
</li>
)
return(
<section class="Index" >
<link rel="stylesheet" href="/styles/admin/index.css" />
<header>
<div class="inner region">
<div class="title">{props.data.setting.page_title}</div>
<form action="/admin/search" method="post">
<select name="admin_search">
<option>ការផ្សាយ</option>
<option>សៀវភៅ</option>
</select>
<input type="text" name="admin_q" required placeholder="Search" />
<input type="submit" value="ស្វែងរក" />
</form>
<div class="logout"><span>{props.data.setting.username}</span> | <a href="/">ទំព័រមុខ</a> | <a href="/logout">ចេញក្រៅ</a></div>
</div>
</header>
<div class="main region">
<div class="sidebar">
<div class="inner">
<a href="/admin"><img src="/images/movie.png" /></a>
<a href="/admin">ការផ្សាយ</a>
<a href="/admin/book"><img src="/images/books.png" /></a>
<a href="/admin/book">សៀវភៅ</a>
<a href="/admin/category"><img src="/images/category.png" /></a>
<a href="/admin/category">ជំពូក</a>
<a href="/admin/upload"><img src="/images/upload.png" /></a>
<a href="/admin/upload">Upload</a>
<a href="/admin/user"><img src="/images/users.png" /></a>
<a href="/admin/user">អ្នកប្រើប្រាស់</a>
<a href="/admin/setting"><img src="/images/setting.png" /></a>
<a href="/admin/setting">Setting</a>
</div>
</div>
<div class="content">
<Page data={props.data} />
</div>
</div>
<div class="footer region">
<div class="info">សរុបទាំងអស់មានចំនួនៈ {props.data.setting.count}</div>
<ul class="list">
{ listItems }
</ul>
<div class="pagination"><img src="/images/load-more.png" /></div>
<div class="credit">© <a href="https://khmerweb.vercel.app/">Khmer Web 2022</a></div>
</div>
</section>
)
}
export default function Index(props: PageProps){
props.data.page = IndexTsx
return(
<Base data={props.data} />
)
}
/* static/styles/admin/index.css */
.Index header{
background: var(--background-dark);
border-bottom: 7px solid var(--background-light);
margin-bottom: 20px;
}
.Index header .inner{
padding: 5px 0;
}
.Index header .inner .title{
font: 30px/1.5 Oswald, Limonf3;
color: orange;
}
.Index header .inner{
display: grid;
grid-template-columns: 25% auto 25%;
align-items: center;
}
.Index header .inner form{
display: grid;
grid-template-columns: 20% auto 20%;
}
.Index header .inner form input,
.Index header .inner form select{
font: var(--body-font);
padding: 2px 5px;
}
.Index header .inner .logout{
text-align: right;
color: white;
}
.Index header .inner .logout a{
color: white;
}
.Index .main{
display: grid;
grid-template-columns: calc(25% - 10px) 75%;
grid-gap: 10px;
}
.Index .main .sidebar{
background: #17a372;
padding: 20px;
}
.Index .main .sidebar .inner{
display: grid;
grid-template-columns: 25% auto;
grid-gap: 20px 10px;
align-items: center;
}
.Index .main .sidebar .inner img{
width: 100%;
float: left;
}
.Index .main .sidebar .inner a{
color: white;
font: 18px/1.5 Oswald, Bayon;
}
.Index .footer .info{
margin-top: 10px;
background: #17a372;
text-align: center;
padding: 10px;
color: white;
}
.Index .footer ul{
list-style-type: none;
display: grid;
grid-template-columns: calc(50% - 5px) calc(50% - 5px);
grid-gap: 10px;
padding: 10px 0;
}
.Index .footer ul li{
display: grid;
grid-template-columns: 25% 60% 15%;
background: #17a372;
align-items: center;
}
.Index .footer ul li .thumb{
display: block;
position: relative;
padding-top: 56.25%;
}
.Index .footer ul li .thumb img{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
float: left;
}
.Index .footer ul li .thumb .play-icon{
position: absolute;
width: 25%;
height: auto;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.Index .footer ul li .title{
padding: 5px 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.Index .footer ul li .title a{
color: white;
font: 17px/1.5 StardosStencil, Limonf3;
}
.Index .footer ul li .edit img{
width: 35px;
margin-right: 5px;
float: left;
visibility: hidden;
}
.Index .footer ul li:hover .edit img{
visibility: visible;
}
.Index .footer .pagination{
text-align: center;
background: #17a372;
padding: 5px 0 0;
}
.Index .footer .pagination img:hover{
cursor: pointer;
opacity: .7;
}
.Index .footer .credit{
text-align: center;
padding: 30px 0;
}
GitHub: https://github.com/Sokhavuth/khmerweb-fresh
Deno Deploy: https://khmerweb.deno.dev/login