Blog Engine with Fresh: Query Post Item
Tags: javascript, webdev, react, fresh
Published: 2022-08-09 10:46:00

{% embed https://dev.to/sokhavuth/table-of-contents-41pd %}
GitHub: [https://github.com/Sokhavuth/deno-fresh](https://github.com/Sokhavuth/deno-fresh)
Deno Deploy: [https://khmerweb-fresh.deno.dev/login](https://khmerweb-fresh.deno.dev/login)
<br/>

```js
// routes/admin/post.jsx

/** @jsx h */
import { h } from "preact";
import VPost from '../../components/admin/post.jsx';
import CPost from "../../controllers/admin/post.js";


export const handler = {
  async GET(req, ctx){
      return await CPost.getPage(req, ctx);
  },  

  async POST(req, ctx){
      return await CPost.createPost(req, ctx);
  },
}


export default function Template(props){
    return (
        <VPost data={ props.data } />
    )
}
```

```js
// controllers/admin/post.js

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 = "Post page";
                    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 = "Login page";
                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();
```

```js
// components/admin/index.jsx

/** @jsx h */
import { h } from "preact";
import Base from "../base.jsx"


function IndexJsx(props){
    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>posts</option>
                  <option>books</option>
                </select>
                <input type="text" name="admin_q" required placeholder="Search" />
                <input type="submit" value="Search" />
              </form>
              <div class="logout"><span>{props.data.setting.username}</span> | <a href="/">Home</a> | <a href="/logout">Logout</a></div>
          </div>
        </header>

        <div class="main region">
          <div class="sidebar">
            <div class="inner">
              <a href="/admin/post"><img src="/images/movie.png" /></a>
              <a href="/admin/post">Post</a>

              <a href="/admin/book"><img src="/images/books.png" /></a>
              <a href="/admin/book">Book</a>

              <a href="/admin/category"><img src="/images/category.png" /></a>
              <a href="/admin/category">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">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">Total number of items: {props.data.setting.count}</div>
          <ul class="list">
              { listItems }
          </ul>
          <div class="pagination"><img src="/images/load-more.png" /></div>
          <div class="credit">&copy; <a href="https://khmerweb.vercel.app/">Khmer Web 2022</a></div>
        </div>
        </section>
    )
}

export default function Index(props){
    props.data.page = IndexJsx
    return(
        <Base data={props.data} />
    )
}
```

```css
/* 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 StardosStencil, 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;
}
```