// 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