Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
4755c1a
專案初始化
WangRping Aug 21, 2023
bb6941c
feat: finish sidebar left views, wait for db created
EasonLu0425 Aug 21, 2023
b9dab14
modify models and add seeders
WangRping Aug 21, 2023
69a69c5
Merge branch 'master' of https://github.com/EasonLu0425/twitter-fulls…
WangRping Aug 21, 2023
2352383
feat:finish recommendation, wait for currentuser function
EasonLu0425 Aug 22, 2023
058929b
finish main page, wait for currentuser fishish to connect the function
EasonLu0425 Aug 22, 2023
2bf9f7f
add user/admin login and register
WangRping Aug 22, 2023
170cc9b
feat: midify tweet li
EasonLu0425 Aug 22, 2023
219eb79
user login / register
WangRping Aug 22, 2023
04a27dd
add user-profile
go2Cshop Aug 22, 2023
dd7ea39
feat:modify user-profile
go2Cshop Aug 22, 2023
a4a31ea
feat: modify user-profile
go2Cshop Aug 22, 2023
39540a3
Merge pull request #1 from EasonLu0425/feature/user-profile
go2Cshop Aug 22, 2023
a6af282
feat: modify user-profile & add upload image
go2Cshop Aug 23, 2023
362c8df
Merge pull request #2 from EasonLu0425/feature/user-profile
go2Cshop Aug 23, 2023
4fa9736
add admin tweets and users page
WangRping Aug 23, 2023
16333c3
....
WangRping Aug 23, 2023
5dfb93e
....
WangRping Aug 23, 2023
ac1ad5a
finish tweet page and controller & finish like, reply, follow function
EasonLu0425 Aug 23, 2023
52de6fe
Merge branch 'tweet-reply'
EasonLu0425 Aug 23, 2023
f0a80e1
merge tweet-reply
EasonLu0425 Aug 23, 2023
ffb8c91
modify missing routes
EasonLu0425 Aug 23, 2023
225e518
add admin delete tweet function and modify admin pages
WangRping Aug 23, 2023
4db4fd0
feat:modify route
go2Cshop Aug 23, 2023
b861fd3
modify login / register page
WangRping Aug 24, 2023
773f763
add hover effect on icon and pass requests/reply & tweet
EasonLu0425 Aug 24, 2023
da99814
Merge branch 'master' of https://github.com/EasonLu0425/twitter-fulls…
EasonLu0425 Aug 24, 2023
3053434
feat: modify following follower partial
go2Cshop Aug 24, 2023
d55992b
feat:modify user-likes page
go2Cshop Aug 24, 2023
9664494
modify get&put settings
WangRping Aug 24, 2023
f0c4670
complete user-profile like function
go2Cshop Aug 24, 2023
b7d846a
feat: add link to user's account and avatar
EasonLu0425 Aug 24, 2023
f343e54
feat:modify user-profile follow button
go2Cshop Aug 24, 2023
9033630
complete merge
go2Cshop Aug 25, 2023
0fdc5b4
feat:complete follow function, and nodify edit profile
go2Cshop Aug 25, 2023
3bd00a8
Merge pull request #3 from EasonLu0425/feature/smfunction
go2Cshop Aug 25, 2023
7b82647
feat: add error alert
EasonLu0425 Aug 25, 2023
7a7b77c
feat: add upload preview function
go2Cshop Aug 25, 2023
1f2c781
feat: modify route
go2Cshop Aug 25, 2023
540e5fc
add infinite scroll to tweets
EasonLu0425 Aug 25, 2023
3974184
Merge branch 'feature/smfunction'
go2Cshop Aug 25, 2023
efa835a
fix some bugs
EasonLu0425 Aug 25, 2023
b663c32
add setting function
WangRping Aug 25, 2023
eac9b53
add setting function
WangRping Aug 25, 2023
18d1691
feat: modify followship route
go2Cshop Aug 25, 2023
02260bc
Merge branch 'master' of https://github.com/EasonLu0425/twitter-fulls…
go2Cshop Aug 25, 2023
fd83065
Merge branch 'feature/cdebug'
go2Cshop Aug 25, 2023
b9ff2e0
feat: modify followship's user
go2Cshop Aug 25, 2023
4220d65
feat: modify follow function & user-replies page
go2Cshop Aug 26, 2023
612e9c4
Merge pull request #4 from EasonLu0425/feature/chdebug
go2Cshop Aug 26, 2023
db5b39c
add alert msg & find infinite scroll bug
EasonLu0425 Aug 26, 2023
75e886f
fix conflict
EasonLu0425 Aug 26, 2023
b7378ac
fix admin sign-in error
WangRping Aug 26, 2023
267e5c0
fix admin sign-in error
WangRping Aug 26, 2023
20536c9
add register/settings input required function
WangRping Aug 26, 2023
a36554b
add register/settings input required function
WangRping Aug 26, 2023
aa9c84d
modify randomUserHelpers
EasonLu0425 Aug 27, 2023
eb76575
feat: modify reply modal user avatar
EasonLu0425 Aug 27, 2023
f493a1a
fix randomUserHelper
EasonLu0425 Aug 27, 2023
123d09f
fix bug : regiter function
WangRping Aug 27, 2023
26a4f22
cant do infinite scroll
EasonLu0425 Aug 27, 2023
fc58560
Merge branch 'master' of https://github.com/EasonLu0425/twitter-fulls…
EasonLu0425 Aug 27, 2023
6780690
feat: remove infinite scroll
EasonLu0425 Aug 27, 2023
94658ad
add validation
go2Cshop Aug 27, 2023
db993c8
Merge pull request #5 from EasonLu0425/feature/cvalidation
go2Cshop Aug 27, 2023
bdf4fe1
fix css
EasonLu0425 Aug 27, 2023
19236bc
feat:add svg
go2Cshop Aug 27, 2023
8f79b83
Merge branch 'feature/cvalidation'
go2Cshop Aug 27, 2023
da6cf93
modify follower/following page
go2Cshop Aug 27, 2023
137392f
Merge pull request #6 from EasonLu0425/feature/chardebug
go2Cshop Aug 27, 2023
fe3ca95
feat:modify svg & css
go2Cshop Aug 28, 2023
cb83e20
Merge pull request #7 from EasonLu0425/feature/chardebug
go2Cshop Aug 28, 2023
fc6a613
feat:modify hbs
go2Cshop Aug 28, 2023
59220fe
Merge pull request #8 from EasonLu0425/feature/chardebug
go2Cshop Aug 28, 2023
4b6628e
feat:modify config for heroku
EasonLu0425 Aug 29, 2023
f2b434b
feat: modify .env
EasonLu0425 Aug 29, 2023
9a9a2ff
modify seeder
EasonLu0425 Aug 29, 2023
bede02c
add readme
WangRping Aug 29, 2023
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=
DEFAULT_AVATAR=
DEFAULT_BACKGROUND=
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,7 @@ typings/
.fusebox/

# DynamoDB Local files
.dynamodb/
.dynamodb/

test.js
default-img.json
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
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Simple Twitter
使用 Node.js 搭配後端框架 Express 打造一個社群網站,可進行貼文﹑回覆其他使用者貼文﹑喜歡貼文﹑追蹤使用者…等功能。
# Features:功能
- 註冊/登入/登出
- 使用者要登入才能使用網站
- 使用者註冊重複/登入/登出失敗時,會看到對應的系統訊
- 使用者
- 使用者能在首頁瀏覽所有的推文
- 使用者點擊貼文方塊時,能查看該則貼文的詳情與回覆串
- 點擊貼文中使用者頭像時,能瀏覽該使用者的個人資料及推文
- 使用者能回覆別人的推文
- 使用者可以追蹤/取消追蹤其他使用者
- 使用者能編輯自己的名稱、介紹、大頭照和個人背景
- 使用者能在首頁的側邊欄,看見跟隨者數量排列前 8 的使用者推薦名單
- 後台管理
- 管理者可以瀏覽站內所有的使用者清單
- 管理者可以瀏覽全站的推文清單 & 刪除

# Environment Setup:環境安裝
- Node.js:v14.16.0
- Express.js:4.16.4
- Express-handlebars:^3.0.0

# Installing Procedure:安裝流程
1.開啟終端機將專案存至本機:
```
git clone https://github.com/EasonLu0425/twitter-fullstack-2020.git
```
2.進入存放此專案的資料夾
```
3.環境變數設定
將根目錄.env.example檔案中輸入imgur金鑰﹑預設頭貼網路位置﹑預設背景網路位置,再把.env.example檔案名稱修改為.env
4.建立資料庫
開啟 MySQL workbench,再連線至本地資料庫,輸入以下建立資料庫

```
drop database if exists ac_twitter_workspace;
create database ac_twitter_workspace;
use ac_twitter_workspace;

drop database if exists ac_twitter_workspace_test;
create database ac_twitter_workspace_test;
use ac_twitter_workspace_test;
```
5.安裝 npm 套件
```
npm install
```
6.db:migrate 設定
```
npx sequelize db:migrate
```
7.加入種子資料
```
npx sequelize db:seed:all
```
8.啟動專案
```
npm run dev
```
9.使用
終端機出現下列訊息" "Example app listening on port 3000!"
可開啟瀏覽器輸入 http://localhost:3000 使用

10.預設使用者 Seed User
- 一般使用者帳號有20組 (帳號:user1﹑user2﹑user3…etc,密碼皆為12345678)
- 管理者帳號僅有1組 (帳號:root ,密碼:12345678)
71 changes: 63 additions & 8 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,68 @@
const express = require('express')
const helpers = require('./_helpers');

const app = express()
const port = 3000
if (process.env.NODE_ENV !== "production") {
require("dotenv").config();
}
const express = require("express");
const handlebarsHelpers = require("./helpers/handlebars-helpers");
const handlebars = require("express-handlebars");
const flash = require("connect-flash");
const session = require("express-session");
const passport = require("./config/passport");
const routes = require("./routes");
const helpers = require("./_helpers");
const path = require("path");
const methodOverride = require("method-override");
const app = express();
const port = process.env.PORT || 3000;
const SESSION_SECRET = "secret";

// 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.set("view engine", "hbs");
app.engine("hbs", handlebars({ extname: ".hbs", helpers: handlebarsHelpers }));
app.use(express.urlencoded({ extended: true }));
app.use(
session({ secret: SESSION_SECRET, resave: false, saveUninitialized: false })
);
app.use(passport.initialize());
app.use(passport.session());
app.use(methodOverride("_method"));
app.use(flash());
app.use("/upload", express.static(path.join(__dirname, "upload"))); //上傳圖片
app.use("/", express.static("public"));
app.use((req, res, next) => {
const { user } = req;
const {
success_messages,
error_messages,
warning_messages,
info_messages,
account_messages,
} = req.flash();

res.locals = {
currentUser: user,
success_messages,
error_messages,
warning_messages,
info_messages,
account_messages,
user: helpers.getUser(req),
paramsUser: req.params.user,
};

// res.locals.currentUser = req.user;
// res.locals.success_messages = req.flash("success_messages");
// res.locals.error_messages = req.flash("error_messages");
// res.locals.warning_messages = req.flash("warning_messages");
// res.locals.info_messages = req.flash("info_messages");
// res.locals.account_messages = req.flash("account_messages");
res.locals.user = helpers.getUser(req);
res.locals.paramsUser = req.params.user;
next();
});

app.use(routes);
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

module.exports = app
module.exports = app;
6 changes: 1 addition & 5 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
"logging": false
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
"use_env_variable": "MYSQL_DATABASE_URL"
},
"travis": {
"username": "travis",
Expand Down
35 changes: 35 additions & 0 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const passport = require('passport')
const LocalStrategy = require('passport-local')
const bcrypt = require('bcryptjs')

const { User } = require('../models')

passport.use(new LocalStrategy(
{
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
},
(req, account, password, cb) => {
User.findOne({ where: { account } })
.then(user => {
if (!user) return cb(null, false, req.flash('account_messages', '帳號不存在!'))
bcrypt.compare(password, user.password).then(res => {
if (!res) return cb(null, false, req.flash('account_messages', '帳號或密碼輸入錯誤!'))
return cb(null, user)
})
})
}
))

passport.serializeUser((user, cb) => {
cb(null, user.id)
})

passport.deserializeUser((id, cb) => {
User.findByPk(id)
.then(user => cb(null, user.toJSON()))
.catch(err => cb(err))
})

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

const adminController = {
signinPage: (req, res) => {
res.render('admin/signin')
},
signin: (req, res) => {
if (req.user.role === 'user') {
req.flash('account_messages', '帳號不存在!')
res.redirect('/admin/signin')
}
req.flash('success_messages', '登入成功')
res.redirect('/admin/tweets')
},
getTweets: (req, res) => {
Tweet.findAll({
include: [
User,
Like,
Reply
]
})
.then(Tweets => {
const data = Tweets.map(tweet => {
const newItem = tweet.toJSON();
newItem.description = newItem.description.substring(0, 50)
newItem.LikeCount = newItem.Likes.length;
newItem.ReplyCount = newItem.Replies.length;
return newItem;
});
res.render('admin/tweets', { tweets: data })
})
},
deleteTweet: (req, res) => {
const { tweetId } = req.params
Tweet.findByPk(tweetId)
.then(tweet => {
if (!tweet) {
console.log('tweet不存在')
res.redirect('back')
}
req.flash("success_messages", "刪除成功!");
return tweet.destroy()
})
.then(() => res.redirect('/admin/tweets'))
},
getUsers: (req, res) => {
User.findAll({
include: [
{ model: Tweet, include: Like },
{ model: User, as: 'Followers' },
{ model: User, as: 'Followings' }
],
order: [
[sequelize.literal('(SELECT COUNT(*) FROM Tweets WHERE Tweets.UserId = User.id)'), 'DESC']
],
where: { role: 'user' }
})
.then(users => {
const data = users.map(user => {
const newUser = user.toJSON()
newUser.likeCount = newUser.Tweets.reduce((totalLikes, tweet) => {
return totalLikes + tweet.Likes.length;
}, 0)
newUser.tweetCount = newUser.Tweets.length
newUser.followingCount = newUser.Followings.length
newUser.followerCount = newUser.Followers.length
return newUser
})
res.render('admin/users', { users: data })
})
}
}

module.exports = adminController
70 changes: 70 additions & 0 deletions controllers/api-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const { User } = require('../models')
const helpers = require('../_helpers')
const { imgurFileHandler } = require('../helpers/file-helpers')

const apiController = {
getUser: async (req, res, next) => {
try {
const currentUser = helpers.getUser(req)
const UserId = req.params.id
const user = await User.findOne({
where: { id: UserId }
})
if (currentUser.id !== user.id) throw new Error('無法觀看編輯其他使用者資料!')

res.json(user.toJSON())
} catch (err) {
req.flash('error_messages', err.message)
res.json({ status: 'error', message: err.message });
next(err)
}
},
putUser: async (req, res, next) => {
try {
const logInUserId = helpers.getUser(req).id
const UserId = req.params.id
const { name } = req.body
const introduction = req.body.introduction || ''
const avatar = req.files ? req.files.avatar : ''
const background = req.files ? req.files.background : ''

let uploadAvatar = ''
let uploadBackground = ''
if (avatar) {
uploadAvatar = await imgurFileHandler(avatar[0])
}
if (background) {
uploadBackground = await imgurFileHandler(background[0])
}
const user = await User.findByPk(UserId)
if (!name) throw new Error('name不可空白')
if (name?.length > 50) throw new Error('name字數超出上限')
if (introduction?.length > 160) throw new Error('introduction字數超出上限')
if (user.id !== logInUserId) throw new Error('無法編輯別人的頁面')

const data = await user.update({
name,
introduction,
avatar: uploadAvatar || user.avatar,
background: uploadBackground || user.background
})
res.json({ status: 'success', message: '已成功更新!', data })
} catch (err) {
req.flash('error_messages', err.message)
res.json({ status: 'error', message: err.message });
next(err)
}
},
uploadImage: async (req, res) => {
const { files } = req
const uploadBackground = await imgurFileHandler(files?.background?.[0])
const uploadAvatar = await imgurFileHandler(files?.avatar?.[0])
res.json({ uploadBackground, uploadAvatar })
},
deleteImage: async (req, res) => {
const uploadBackground = helpers.getUser(req).background || DEFAULT_BACKGROUND
res.json({ uploadBackground })
}
}

module.exports = apiController
Loading