Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
IMGUR_CLIENT_ID=
PORT=
SESSION_SECRET=
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules/*
/test/*
12 changes: 12 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
env:
browser: true
commonjs: true
es2021: true
extends:
- standard
parserOptions:
ecmaVersion: 12
rules:
arrow-parens:
- warn
- as-needed
53 changes: 53 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
* text=auto

# Source code
*.css text diff=css
*.html text diff=html
*.js text
*.json text

# Documentation
*.markdown text diff=markdown
*.md text diff=markdown
*.txt text

# Templates
*.handlebars text
*.hbs text

# Configs
*.cnf text
*.conf text
*.config text
.editorconfig text
.env text
.env.* text
.gitattributes text
.gitconfig text
*.lock text -diff
package.json text eol=lf
package-lock.json text -diff
yarn.lock text -diff
*.yaml text
*.yml text
browserslist text

# Heroku
Procfile text

# Graphics
*.gif binary
*.ico binary
*.jpg binary
*.jpeg binary
*.pdf binary
*.png binary
# SVG treated as an asset (binary) by default.
*.svg text
# If you want to treat it as binary,
# use the following line instead.
# *.svg binary
*.webp binary

# Ignore files (like .npmignore or .gitignore)
*.*ignore text
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"signin"
]
}
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: NODE_ENV=production node app.js
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Simple Twitter

## **Introduction 專案簡介**

使用 Node.js + Express + MySQL 製作的簡易社群網站,使用者可以註冊帳號、登入,並進行發文、瀏覽他人推文、回覆他人留言、對別人的推文按 Like,以及追蹤其他使用者等。

(待補:網站截圖)

## **Features 功能**

- 使用者可以註冊,並登入帳號使用網站服務
- 使用者能在首頁瀏覽所有的推文 (tweet)
- 使用者能新增推文
- 使用者能回覆別人的推文
- 使用者能對別人的推文按 Like/Unlike
- 使用者可以追蹤/取消追蹤其他使用者
- 使用者能編輯自己的名稱、自我介紹、個人頭像與封面

## **Prerequisites 環境設置**

- [VScode](https://code.visualstudio.com/)
- [Git](https://git-scm.com/downloads)
- [Node.js](https://nodejs.org/en/)
- [nodemon](https://www.npmjs.com/package/nodemon)
- [MySQL Workbench](https://dev.mysql.com/downloads/workbench/)

## **Installation 開始使用**

```
# 開啟終端機 並 Clone 此專案至本機
$ git clone https://github.com/thpss91103/twitter-fullstack-2023.git

# 於終端機進入存放本專案的資料夾
$ cd xxxxx

# 安裝 npm 套件
$ npm install

# 新增.env檔案,並請根據.env.example檔案內資訊設置環境變數

# 修改 config.json 中的 development 設定,使用個人 MySQL 的 username、password 和 database
development": {
"username": "<your username>",
"password": "<your password>",
"database": "<your database>",
"host": "127.0.0.1",
"dialect": "mysql"
}

# 新增資料表和種子資料
$ npx sequelize db:migrate
$ npx sequelize db:seed:all

# 啟動伺服器,執行 app.js 檔案
$ npm run dev

# 若在終端機看到下方訊息代表順利運行,於瀏覽器中輸入該網址(http://localhost:3000)即可開始使用本網站
"App is running on http://localhost:3000"
```

## **Contributors 貢獻者**

(待補)
12 changes: 0 additions & 12 deletions _helpers.js

This file was deleted.

40 changes: 36 additions & 4 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}

const express = require('express')
const helpers = require('./_helpers');
const exphbs = require('express-handlebars')
const routes = require('./routes')
const methodOverride = require('method-override')
const session = require('express-session')
const passport = require('./config/passport')
const flash = require('connect-flash')
const helpers = require('./helpers/auth-helpers')
const handlebarsHelpers = require('./helpers/handlebars-helpers')

const app = express()
const port = 3000
const port = process.env.PORT || 3000

app.engine('hbs', exphbs({ defaultLayout: 'main', extname: '.hbs', helpers: handlebarsHelpers }))
app.set('view engine', 'hbs')

app.use(express.static('public'))
app.use(express.urlencoded({ extended: true}))
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
app.use(flash())
app.use(methodOverride('_method'))
app.use((req, res, next) => {
res.locals.success_messages = req.flash('success_messages')
res.locals.error_messages = req.flash('error_messages')
res.locals.user = helpers.getUser(req)
next()
})
// use helpers.getUser(req) to replace req.user
// use helpers.ensureAuthenticated(req) to replace req.isAuthenticated()

app.get('/', (req, res) => res.send('Hello World!'))
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
app.use(routes)

app.listen(port, () => console.log(`App is running on http://localhost:${port}`))

module.exports = app
3 changes: 3 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const passport = require('passport')

module.exports = passport
16 changes: 16 additions & 0 deletions controllers/tweet-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { Tweet, User } = require('../models')

const tweetController = {
getTweets: async (req, res, next) => {
try {
const tweets = await Tweet.findAll({ raw: true, nest: true, include: [User] })
const sortedTweets = tweets.sort((a, b) => b.createdAt - a.createdAt)
return res.render('tweets', { tweets: sortedTweets })
} catch (err) {
next(err)
}
},
postTweet: async (res, req, nex) => {}
}

module.exports = tweetController
51 changes: 51 additions & 0 deletions controllers/user-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const bcrypt = require('bcryptjs')
const { Op } = require('sequelize')
const { User } = require('../models')
const userController = {
signUpPage: (req, res) => {
res.render('signup')
},
signUp: async (req, res, next) => {
try {
const { account, name, email, password, checkPassword } = req.body
const errors = []

if (!account || !name || !email || !password || !checkPassword) {
errors.push('所有欄位皆為必填')
}

const findUser = await User.findOne({
where: { [Op.or]: [{ account }, { email }] }, // Op.or: 表示接下來陣列內的條件之一成立即可
attributes: ['account', 'email'] // 若有找到,只返回 account 和 email 屬性即可
})

if (findUser && findUser.account === account) {
errors.push('此帳號已被註冊')
}
if (name.length > 50) {
errors.push('名稱不能超過 50 個字')
}
if (findUser && findUser.email === email) {
errors.push('此 Email 已被註冊')
}
if (password !== checkPassword) {
errors.push('兩次輸入的密碼不相同')
}
if (errors.length > 0) {
throw new Error(errors.join('\n & \n'))
}

const hash = await bcrypt.hash(req.body.password, 10)
await User.create({ account, name, email, password: hash })

req.flash('success_messages', '成功註冊帳號!')
return res.redirect('/signin')
} catch (err) {
next(err)
}
},
signInPage: (req, res) => {
res.render('signin')
}
}
module.exports = userController
12 changes: 12 additions & 0 deletions helpers/auth-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function ensureAuthenticated (req) {
return req.isAuthenticated()
}

function getUser (req) {
return req.user
}

module.exports = {
ensureAuthenticated,
getUser
}
10 changes: 10 additions & 0 deletions helpers/handlebars-helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const dayjs = require('dayjs')
const relativeTime = require('dayjs/plugin/relativeTime')
dayjs.extend(relativeTime)

module.exports = {
relativeTimeFromNow: a => dayjs(a).fromNow(),
ifCond: function (a, b, options) {
return a === b ? options.fn(this) : options.inverse(this)
}
}
11 changes: 11 additions & 0 deletions middleware/error-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
generalErrorHandler (err, req, res, next) {
if (err instanceof Error) {
req.flash('error_messages', `${err.message}`)
} else {
req.flash('error_messages', `${err}`)
}
res.redirect('back')
next(err)
}
}
16 changes: 8 additions & 8 deletions migrations/20190115071418-create-followship.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use strict';
'use strict'
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Followships', {
Expand All @@ -8,23 +8,23 @@ module.exports = {
primaryKey: true,
type: Sequelize.INTEGER
},
followerId: {
follower_id: {
type: Sequelize.INTEGER
},
followingId: {
following_id: {
type: Sequelize.INTEGER
},
createdAt: {
created_at: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
updated_at: {
allowNull: false,
type: Sequelize.DATE
}
});
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Followships');
return queryInterface.dropTable('Followships')
}
};
}
Loading