Skip to content

Midterm

Rshark edited this page Jan 9, 2024 · 4 revisions

全複製自參考網站 已閱讀完畢且理解 參考資料

*app.js

import { Application, Router } from "https://deno.land/x/oak/mod.ts";
import { DB } from "https://deno.land/x/sqlite/mod.ts";
import { Session } from "https://deno.land/x/oak_sessions/mod.ts";
import * as render from './render.js';

const db = new DB("blog.db");
db.query("CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, title TEXT, body TEXT)");
db.query("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT, password TEXT, email TEXT)");

const router = new Router();

router.get('/', list)
  .get('/signup', signupUi)
  .post('/signup', signup)
  .get('/login', loginUi)
  .post('/login', login)
  .get('/logout', logout)
  .get('/contact/search', search)
  .get('/contact/new', add)
  .get('/contact/:id', show)
  .post('/contact', create)
  .post('/search', find)
  .get('/contact/delete/:id', deleteConfirmation)
  .post('/contact/delete/:id', deleteContact);

const app = new Application();
app.use(Session.initMiddleware());
app.use(router.routes());
app.use(router.allowedMethods());

function sqlcmd(sql, arg1) {
  console.log('sql:', sql)
  try {
    var results = db.query(sql, arg1)
    console.log('sqlcmd: results=', results)
    return results
  } catch (error) {
    console.log('sqlcmd error: ', error)
    throw error
  }
}

function postQuery(sql) {
  let list = []
  for (const [id, username, title, body] of sqlcmd(sql)) {
    list.push({id, username, title, body})
  }
  console.log('postQuery: list=', list)
  return list
}

function userQuery(sql) {
  let list = []
  for (const [id, username, password, email] of sqlcmd(sql)) {
    list.push({id, username, password, email})
  }
  console.log('userQuery: list=', list)
  return list
}

async function parseFormBody(body) {
  const pairs = await body.value
  const obj = {}
  for (const [key, value] of pairs) {
    obj[key] = value
  }
  return obj
}

async function signupUi(ctx) {
  ctx.response.body = await render.signupUi();
}

async function signup(ctx) {
  const body = ctx.request.body()
  if (body.type === "form") {
    var user = await parseFormBody(body)
    var dbUsers = userQuery(`SELECT id, username, password, email FROM users WHERE username='${user.username}'`)
    if (dbUsers.length === 0) {
      sqlcmd("INSERT INTO users (username, password, email) VALUES (?, ?, ?)", [user.username, user.password, user.email]);
      ctx.response.body = render.success()
    } else 
      ctx.response.body = render.fail()
  }
}

async function loginUi(ctx) {
  ctx.response.body = await render.loginUi();
}

async function login(ctx) {
  const body = ctx.request.body();
  if (body.type === "form") {
    const userCredentials = await parseFormBody(body);
    const dbUsers = userQuery(`SELECT id, username, password, email FROM users WHERE username='${userCredentials.username}'`);
    
    if (dbUsers.length > 0) {
      const dbUser = dbUsers[0];
      if (dbUser.password === userCredentials.password) {
        await ctx.state.session.set('user', { username: dbUser.username, id: dbUser.id }); // Set the user in the session
        console.log('session.user=', await ctx.state.session.get('user'));
        ctx.response.redirect('/');
      } else {
        ctx.response.body = render.fail();
      }
    } else {
      ctx.response.body = render.userNotFound();
    }
  }
}

async function logout(ctx) {
  await ctx.state.session.set('user', null); // Clear the user in the session
  ctx.response.redirect('/');
}

async function list(ctx) {
  const posts = postQuery("SELECT id, username, title, body FROM posts");
  const user = await ctx.state.session.get('user');

  console.log('list: user=', user);
  console.log('list: posts=', posts);

  ctx.response.body = await render.list(posts, user);
}


async function add(ctx) {
  var user = await ctx.state.session.get('user')
  if (user != null) {
    ctx.response.body = await render.newPost();
  } else {
    ctx.response.body = render.fail()
  }
}

async function search(ctx) {
  ctx.response.body = await render.search();
}

async function show(ctx) {
  const pid = ctx.params.id;
  let posts = postQuery(`SELECT id, username, title, body FROM posts WHERE id=${pid}`)
  let post = posts[0]
  console.log('show:post=', post)
  if (!post) ctx.throw(404, 'invalid post id');
  ctx.response.body = await render.show(post);
}

async function create(ctx) {
  const body = ctx.request.body()
  if (body.type === "form") {
    var post = await parseFormBody(body)
    console.log('create:post=', post)
    var user = await ctx.state.session.get('user')
    if (user != null) {
      console.log('user=', user)
      sqlcmd("INSERT INTO posts (username, title, body) VALUES (?, ?, ?)", [user.username, post.title, post.body]);  
    } else {
      ctx.throw(404, 'not login yet!');
    }
    ctx.response.redirect('/');
  }
}

async function find(ctx) {
  const body = ctx.request.body();
  if (body.type === "form") {
    const pairs = await body.value;
    const searchTerm = pairs.get('name');
    const results = [];
    let posts = postQuery("SELECT id, username, title, body FROM posts");

    for (const post of posts) {
      if (post.title.toLowerCase().includes(searchTerm.toLowerCase())) {
        results.push(post);
      }
    }    

    console.log('Search Term:', searchTerm);

    if (results.length > 0) {
      const resultHtml = results.map(post => `<h1>Name:${post.title}</h1><p>Tel:${post.body}</p>`).join('');
      ctx.response.body = await render.found(resultHtml);
    } else {
      ctx.response.body = await render.not_found();
    }
  } 
}

async function deleteConfirmation(ctx) {
  const pid = ctx.params.id;
  const post = postQuery(`SELECT id, username, title, body FROM posts WHERE id=${pid}`)[0];

  if (!post) {
    ctx.throw(404, 'Invalid post id');
  }

  ctx.response.body = await render.deleteConfirmation(post);
}

async function deleteContact(ctx) {
  const pid = ctx.params.id;
  sqlcmd("DELETE FROM posts WHERE id=?", [pid]);

  ctx.response.redirect('/');
}

console.log('Server run at http://127.0.0.1:8000')
await app.listen({ port: 8000 });

blog.db

*render.js

export function layout(title, content, user) {
  return `
    <html>
    <head>
      <title>${title}</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" integrity="sha512-zoF5xaUrnT5Vps1ttX2iCP6B6yA1keRdB9Qo1uWEs21F9vRoLybpBm8xD5NngA6gPLFV/hgtfJC8ed8Cq58wuQ==" crossorigin="anonymous" />
      <style>
        body {
          font-family: 'Arial', sans-serif;
          background-color: #f0f0f0;
          margin: 0;
          padding: 0;
        }

        header {
          background-color: #4285f4;
          color: #fff;
          text-align: center;
          padding: 1em 0;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }

        header a {
          text-decoration: none;
          color: #fff;
          padding: 0.5em 1em;
          transition: background-color 0.3s ease;
        }

        header a:hover {
          background-color: #3367d6;
        }

        main {
          max-width: 600px;
          margin: 20px auto;
          background-color: #fff;
          padding: 20px;
          border-radius: 5px;
          box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        form {
          text-align: center;
          padding: 10px;
          border: 1px solid #ccc;
          border-radius: 5px;
          margin-bottom: 20px;
        }

        form input {
          width: calc(100% - 20px);
          padding: 10px;
          margin-bottom: 10px;
          box-sizing: border-box;
          border: 1px solid #ccc;
          border-radius: 5px;
        }

        form input[type=submit], button {
          background-color: #4285f4;
          color: #fff;
          border: none;
          padding: 10px 20px;
          font-size: 16px;
          border-radius: 5px;
          cursor: pointer;
          transition: background-color 0.3s ease, transform 0.2s ease;
        }

        form input[type=submit]:hover, button:hover {
          background-color: #3367d6;
          transform: scale(1.05);
        }

        a {
          color: #4285f4;
          text-decoration: none;
        }

        a:hover {
          text-decoration: underline;
        }

        .add-contact-button {
          background-color: #4caf50;
          color: #fff;
          border: none;
          padding: 10px 20px;
          font-size: 16px;
          border-radius: 5px;
          cursor: pointer;
          transition: background-color 0.3s ease, transform 0.2s ease;
          margin-top: 20px; /* Adjust margin as needed */
        }

        .add-contact-button:hover {
          background-color: #45a049;
          transform: scale(1.05);
        }
      </style>
    </head>
    <body>
      <header>
        <div>
          <button onclick="window.location.href='/'">Home</button>
        </div>
        <h1>${title}</h1>
        <div>
          ${user ? `<button onclick="window.location.href='/logout'">Logout</button>` : `<button onclick="window.location.href='/login'">Login</button>`}
        </div>
      </header>
      <main>
        <section id="content">
          ${content}
        </section>
      </main>
    </body>
    </html>
  `;
}
  
  
  export function loginUi() {
    return layout('Login', `
      <form action="/login" method="post">
        <input type="text" placeholder="Username" name="username">
        <input type="password" placeholder="Password" name="password">
        <input type="submit" value="Login">
      </form>
      <p>New User? <a href="/signup">Create an account</a></p>
    `);
  }
  
  export function signupUi() {
    return layout('Signup', `
      <form action="/signup" method="post">
        <input type="text" placeholder="Username" name="username">
        <input type="password" placeholder="Password" name="password">
        <input type="text" placeholder="E-mail" name="email">
        <input type="submit" value="Signup">
      </form>
    `);
  }
  
  export function success() {
    return layout('Success!', `
      You may <a href="/">view all Contacts</a> / <a href="/login">login</a> again!
    `);
  }
  
  export function fail() {
    return layout('Fail!', `
      You may <a href="/">view all Contacts</a> or <a href="JavaScript:window.history.back()">go back</a>!
    `);
  }
  
  export function userNotFound() {
    return layout('User Not Found!', `
      The provided username does not exist. Please check your username and try again.
      <p>You may <a href="/">view all Contacts</a> or <a href="JavaScript:window.history.back()">go back</a>!
    `);
  }
  
// ...

export function list(posts, user) {
  let list = posts.map(post => {
    const deleteLink = (user && post.username === user.username)
      ? ` | <a href="/contact/delete/${post.id}"><i class="fas fa-trash-alt"></i> Delete</a>`
      : '';

    return `
      <li>
        <h2><a href="/contact/${post.id}">${post.title} -- by ${post.username}</a></h2>
        <p>
          <a href="/contact/${post.id}">View contact</a>
          ${deleteLink}
        </p>
      </li>
    `;
  });

  let content = `
    <h1>Contacts</h1>
    <div id="search-bar">
      <form action="/search" method="post">
        <input type="text" placeholder="Search Contacts" name="name" required>
        <input type="submit" value="Search">
      </form>
    </div>
    <p>${(user == null) ? '<a href="/login">Login</a> to Add a Contact!' : `Welcome ${user.username}!`}</p>
    <button onclick="window.location.href='/contact/new'" class="add-contact-button">Add a Contact</button> <!-- Add this line -->
    <p>There are <strong>${posts.length}</strong> Contacts!</p>
    <ul id="posts">
      ${list.join('\n')}
    </ul>
  `;

  return layout('Directory', content, user); // Pass user to layout function
}
  
  export function newPost() {
    return layout('New Contact', `
      <h1>New Contact</h1>
      <p>Add a new contact.</p>
      <form action="/contact" method="post">
        <p><input type="text" placeholder="Name" name="title"></p>
        <p><textarea placeholder="Tel" name="body"></textarea></p>
        <p><input type="submit" value="Create"></p>
      </form>
      <button onclick="window.location.href='/contact/new'" class="add-contact-button">Add a Contact</button>
    `);
  }

  export function search() {
    return layout('Query Contact person', `
    <h1>Search Contacts</h1>
    <form action="/search" method="post">
      <p><input type="text" placeholder="Name" name="name" required></p>
      <p><input type="submit" value="Search"></p>
    </form>
    `)
  }

  export function found(resultHtml) {
    return layout('Search results', `
      <h1>Search Contacts</h1>
      <form action="/search" method="post">
        <p><input type="text" placeholder="Name" name="name"></p>
        <p><input type="submit" value="Search"></p>
      </form>
      ${resultHtml}
    `);
  }
  
  
  export function not_found() {
    return layout('Search results',
      `
    <h1>Search Contacts</h1>
    <form action="/search" method="post">
      <p><input type="text" placeholder="Name" name="name"></p>
      <p><input type="submit" value="Search"></p>
    </form>
    <h1>Not Found</h1>
    `,
    );
  }

  export function deleteConfirmation(post) {
    return layout('Confirm Deletion', `
      <h1>Delete Confirmation</h1>
      <p>Are you sure you want to delete the contact?</p>
      <p><strong>${post.title}</strong> -- by ${post.username}</p>
      <form action="/contact/delete/${post.id}" method="post">
      <p><input type="submit" value="Delete"></p>
      </form>
    `);
  }

  
  export function show(post) {
    const deleteLink = `<a href="/contact/delete/${post.id}">Delete</a>`;
    return layout(post.title, `
      <h1>${post.title} -- by ${post.username}</h1>
      <p>${post.body}</p>
      <p>${deleteLink}</p>
    `);
  }
Clone this wiki locally