Spring boot ตอนที่ 3 – กรณีศึกษา MyFacebook web (4)

spring-boot-logo
บทสุดท้ายของกรณีศึกษาแล้วครับ เนื่องจากจะยาวเกินไป และเนื้อหาก็เป็นการใช้ spring mvc, security, jpa ซึ่งเป็นเรื่องที่นอกเหนือจาก spring boot ในบทนี้จะทำในเรื่อง Add friend และ comment post ของเพื่อนที่เราได้ Add ไปแล้ว โดยผมไม่ได้ทำเหมือน facebook ที่ต้องกด approve เพื่อเป็นการประหยัดเวลาครับ คือ add แล้วก็มีเพื่อนได้เลย


updated#1
รวมลิงก์ของทุกตอนครับ

updated#2
ตอนนี้ผมกำลังทำ video ลง youtube อยู่ครับ

  1. แก้ไข User.java ให้รองรับเพื่อน
    package com.magicalcyber.myfacebook.model;
    
    import java.sql.Timestamp;
    import java.util.Set;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import javax.persistence.OneToOne;
    import javax.persistence.OrderBy;
    import javax.persistence.Table;
    import javax.validation.constraints.Size;
    
    @Entity
    @Table(name = "user")
    public class User {
    
    	@Id
    	@GeneratedValue
    	private Long id;
    
    	@Column
    	@Size(min = 5, max = 64)
    	private String email;
    
    	@Column
    	@Size(min = 5, max = 32)
    	private String name;
    
    	@Column
    	@Size(min = 5, max = 64)
    	private String password;
    
    	@Column
    	private Timestamp createdDate;
    
    	@OneToOne
    	private Role role;
    
    	@OneToMany(cascade = CascadeType.ALL)
    	@OrderBy("createdDate")
    	private Set<User> friends;
    
    	public Role getRole() {
    		return role;
    	}
    
    	public void setRole(Role role) {
    		this.role = role;
    	}
    
    	public Long getId() {
    		return id;
    	}
    
    	public void setId(Long id) {
    		this.id = id;
    	}
    
    	public String getEmail() {
    		return email;
    	}
    
    	public void setEmail(String email) {
    		this.email = email;
    	}
    
    	public String getPassword() {
    		return password;
    	}
    
    	public void setPassword(String password) {
    		this.password = password;
    	}
    
    	public Timestamp getCreatedDate() {
    		return createdDate;
    	}
    
    	public void setCreatedDate(Timestamp createdDate) {
    		this.createdDate = createdDate;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public Set<User> getFriends() {
    		return friends;
    	}
    
    	public void setFriends(Set<User> friends) {
    		this.friends = friends;
    	}
    
    }
    
    
  2. ข้อนี้เป็นข้อผิดพลาดของผมเองง ให้แก้ไขประเภทตัวแปร comment ใน Post.java จาก Set ให้เป็น List
    package com.magicalcyber.myfacebook.model;
    
    import java.sql.Timestamp;
    import java.util.List;
    
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.ManyToOne;
    import javax.persistence.OneToMany;
    import javax.persistence.OrderBy;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "post")
    public class Post {
    
    	@Id
    	@GeneratedValue
    	private Long id;
    
    	@Column
    	private String content;
    
    	@Column
    	private Timestamp createdDate;
    
    	@ManyToOne
    	private User createdUser;
    
    	@OneToMany(cascade = CascadeType.ALL)
    	@OrderBy("createdDate")
    	private List<Comment> comments;
    
    	public Long getId() {
    		return id;
    	}
    
    	public void setId(Long id) {
    		this.id = id;
    	}
    
    	public String getContent() {
    		return content;
    	}
    
    	public void setContent(String content) {
    		this.content = content;
    	}
    
    	public Timestamp getCreatedDate() {
    		return createdDate;
    	}
    
    	public void setCreatedDate(Timestamp createdDate) {
    		this.createdDate = createdDate;
    	}
    
    	public User getCreatedUser() {
    		return createdUser;
    	}
    
    	public void setCreatedUser(User createdUser) {
    		this.createdUser = createdUser;
    	}
    
    	public List<Comment> getComments() {
    		return comments;
    	}
    
    	public void setComments(List<Comment> comments) {
    		this.comments = comments;
    	}
    
    }
    
    
  3. สร้าง FriendDto.java เพื่อรองรับการค้นหาเพื่อน

    package com.magicalcyber.myfacebook.dto;
    
    public class FriendDto {
    
    	private Long userId;
    	private String name;
    	private boolean friend;
    
    	public FriendDto() {
    	}
    
    	public FriendDto(Long userId, String name, boolean friend) {
    		this.userId = userId;
    		this.name = name;
    		this.friend = friend;
    	}
    
    	public Long getUserId() {
    		return userId;
    	}
    
    	public void setUserId(Long userId) {
    		this.userId = userId;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public boolean isFriend() {
    		return friend;
    	}
    
    	public void setFriend(boolean friend) {
    		this.friend = friend;
    	}
    
    }
    
    
  4. แก้ไข PostRepository ให้รองรับ post ของเพื่อนด้วย
    package com.magicalcyber.myfacebook.repository;
    
    import java.util.List;
    import java.util.Set;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import com.magicalcyber.myfacebook.model.Post;
    import com.magicalcyber.myfacebook.model.User;
    
    public interface PostRepository extends JpaRepository<Post, Long> {
    	List<Post> findByCreatedUserInOrderByCreatedDateDesc(Set<User> users);
    }
    
    
  5. ปรับ UserRepository ให้รอบรับการค้นหาเพื่่อนจากชื่อ
    package com.magicalcyber.myfacebook.repository;
    
    import java.util.List;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import com.magicalcyber.myfacebook.model.User;
    
    public interface UserRepository extends JpaRepository<User, Long> {
    	User findByEmail(String email);
    
    	List<User> findByNameContainingOrEmail(String name, String email);
    
    }
    
    
  6. แก้ไข PostServiceImpl ให้ list post ของเพื่อนด้วย

    package com.magicalcyber.myfacebook.service;
    
    import java.sql.Timestamp;
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.magicalcyber.myfacebook.model.Comment;
    import com.magicalcyber.myfacebook.model.Post;
    import com.magicalcyber.myfacebook.model.User;
    import com.magicalcyber.myfacebook.repository.PostRepository;
    import com.magicalcyber.myfacebook.repository.UserRepository;
    import com.magicalcyber.myfacebook.web.form.CommentForm;
    import com.magicalcyber.myfacebook.web.form.PostForm;
    
    @Service("postService")
    public class PostServiceImpl implements PostService {
    
    	@Autowired
    	private PostRepository postRepository;
    
    	@Autowired
    	private UserRepository userRepository;
    
    	@Override
    	public void save(PostForm postForm) {
    		User currentUser = userRepository.getOne(postForm.getUserId());
    		Post post = new Post();
    		post.setCreatedUser(currentUser);
    		post.setCreatedDate(new Timestamp(System.currentTimeMillis()));
    		post.setContent(postForm.getContent());
    		postRepository.save(post);
    	}
    
    	@Override
    	public List<Post> listPost(Long userId) {
    
    		// get current user
    		User currentUser = userRepository.findOne(userId);
    
    		HashSet<User> users = new HashSet<>();
    		if (currentUser.getFriends() != null && !currentUser.getFriends().isEmpty()) {
    			users = new HashSet<>(currentUser.getFriends());
    		}
    		users.add(currentUser);
    
    		return postRepository.findByCreatedUserInOrderByCreatedDateDesc(users);
    	}
    
    	@Override
    	public void saveComment(CommentForm commentForm) {
    		Post post = postRepository.findOne(commentForm.getPostId());
    		if (post == null) {
    			throw new IllegalArgumentException("Not found this post");
    		} else {
    			if (post.getComments() == null) {
    				post.setComments(new ArrayList<>());
    			}
    
    			User commentUser = userRepository.findOne(commentForm.getUserId());
    
    			Comment comment = new Comment();
    			comment.setCreatedUser(commentUser);
    			comment.setContent(commentForm.getContent());
    			comment.setCreatedDate(new Timestamp(System.currentTimeMillis()));
    
    			post.getComments().add(comment);
    			postRepository.save(post);
    		}
    	}
    
    }
    
    
  7. แก้ไข UserService ให้รองรับการ add/remove เพื่อน

    package com.magicalcyber.myfacebook.service;
    
    import java.util.Set;
    
    import com.magicalcyber.myfacebook.constant.UserRole;
    import com.magicalcyber.myfacebook.dto.FriendDto;
    import com.magicalcyber.myfacebook.model.User;
    import com.magicalcyber.myfacebook.web.form.FriendForm;
    import com.magicalcyber.myfacebook.web.form.RegisterForm;
    
    public interface UserService {
    	void create(RegisterForm form, UserRole userRole);
    
    	User findByEmail(String email);
    
    	Set<FriendDto> searchFriend(FriendForm friendForm);
    
    	void addFriend(Long userId);
    
    	void removeFriend(Long userId);
    
    	void addFriend(Long userId, Long friendId);
    
    }
    
    

    UserServiceImpl

    package com.magicalcyber.myfacebook.service;
    
    import java.sql.Timestamp;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import com.magicalcyber.myfacebook.constant.UserRole;
    import com.magicalcyber.myfacebook.dto.FriendDto;
    import com.magicalcyber.myfacebook.model.CustomUserDetail;
    import com.magicalcyber.myfacebook.model.User;
    import com.magicalcyber.myfacebook.repository.RoleRepository;
    import com.magicalcyber.myfacebook.repository.UserRepository;
    import com.magicalcyber.myfacebook.web.form.FriendForm;
    import com.magicalcyber.myfacebook.web.form.RegisterForm;
    
    @Service
    public class UserServiceImpl implements UserService {
    
    	@Autowired
    	private UserRepository userRepository;
    
    	@Autowired
    	private RoleRepository roleRepository;
    
    	@Autowired
    	private PasswordEncoder passwordEncoder;
    
    	@Override
    	public void create(RegisterForm form, UserRole userRole) {
    		User user = new User();
    		user.setName(form.getName());
    		user.setEmail(form.getEmail());
    		user.setPassword(passwordEncoder.encode(form.getPassword()));
    		user.setRole(roleRepository.findOne(userRole.name()));
    		user.setCreatedDate(new Timestamp(System.currentTimeMillis()));
    		userRepository.save(user);
    	}
    
    	@Override
    	public User findByEmail(String email) {
    		return userRepository.findByEmail(email);
    	}
    
    	@Override
    	public Set<FriendDto> searchFriend(FriendForm friendForm) {
    		// set cache for check already friend
    		HashMap<Long, User> friendCache = new HashMap<>();
    		User currentUser = userRepository.findOne(friendForm.getUserId());
    		Set<User> currentFriends = currentUser.getFriends();
    		if (currentFriends != null && !currentFriends.isEmpty()) {
    			for (User friend : currentFriends) {
    				friendCache.put(friend.getId(), friend);
    			}
    		}
    
    		// set user and check already friend
    		Set<FriendDto> userList = new HashSet<>();
    
    		List<User> users = userRepository.findByNameContainingOrEmail(friendForm.getKeyword(), friendForm.getKeyword());
    		if (users != null && !users.isEmpty()) {
    			for (User user : users) {
    				FriendDto friend = new FriendDto();
    				friend.setUserId(user.getId());
    				friend.setName(user.getName());
    				friend.setFriend(friendCache.containsKey(user.getId()));
    				userList.add(friend);
    			}
    		}
    		return userList;
    	}
    
    	@Override
    	public void addFriend(Long userId) {
    		// get current user
    		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    		CustomUserDetail userDetail = (CustomUserDetail) auth.getPrincipal();
    		User currentUser = userRepository.findOne(userDetail.getId());
    
    		// add current friend
    		User requestedFriend = userRepository.findOne(userId);
    		if (currentUser.getFriends() == null) {
    			currentUser.setFriends(new HashSet<>());
    		}
    		currentUser.getFriends().add(requestedFriend);
    		userRepository.save(currentUser);
    
    		// add requested friend
    		if (requestedFriend.getFriends() == null) {
    			requestedFriend.setFriends(new HashSet<>());
    		}
    		requestedFriend.getFriends().add(currentUser);
    		userRepository.save(requestedFriend);
    	}
    
    	@Override
    	public void addFriend(Long userId, Long friendId) {
    		// get current user
    		User currentUser = userRepository.findOne(userId);
    
    		// add current friend
    		User requestedFriend = userRepository.findOne(friendId);
    		if (currentUser.getFriends() == null) {
    			currentUser.setFriends(new HashSet<>());
    		}
    		currentUser.getFriends().add(requestedFriend);
    		userRepository.save(currentUser);
    
    		// add requested friend
    		if (requestedFriend.getFriends() == null) {
    			requestedFriend.setFriends(new HashSet<>());
    		}
    		requestedFriend.getFriends().add(currentUser);
    		userRepository.save(requestedFriend);
    	}
    
    	@Override
    	public void removeFriend(Long userId) {
    		// get current user
    		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    		CustomUserDetail userDetail = (CustomUserDetail) auth.getPrincipal();
    		User currentUser = userRepository.findOne(userDetail.getId());
    
    		// remove current friend
    		User requestedFriend = userRepository.findOne(userId);
    		if (currentUser.getFriends() != null) {
    			currentUser.getFriends().remove(requestedFriend);
    		}
    		userRepository.save(currentUser);
    
    		// remove requested friend
    		if (requestedFriend.getFriends() != null) {
    			requestedFriend.getFriends().remove(currentUser);
    		}
    		userRepository.save(requestedFriend);
    	}
    
    }
    
    
  8. เพิ่ม FriendController

    package com.magicalcyber.myfacebook.web.controller;
    
    import javax.validation.Valid;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import com.magicalcyber.myfacebook.model.CustomUserDetail;
    import com.magicalcyber.myfacebook.service.UserService;
    import com.magicalcyber.myfacebook.web.form.FriendForm;
    
    @Controller
    public class FriendController {
    
    	private static final Logger log = LoggerFactory.getLogger(FriendController.class);
    
    	@Autowired
    	private UserService userService;
    
    	@GetMapping("/friend")
    	String friends(Model model) {
    
    		// get current user
    		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    
    		CustomUserDetail currentUser = (CustomUserDetail) auth.getPrincipal();
    
    		FriendForm friendForm = new FriendForm();
    		friendForm.setUserId(currentUser.getId());
    		model.addAttribute("friendForm", friendForm);
    		return "friend";
    	}
    
    	@GetMapping("/friend/search")
    	String searchFriends(@Valid @ModelAttribute("friendForm") FriendForm friendForm, BindingResult result,
    			Model model) {
    		if (!result.hasErrors()) {
    			model.addAttribute("users", userService.searchFriend(friendForm));
    		}
    		return "friend";
    	}
    
    	@PostMapping("/friend/add")
    	String addFriend(@RequestParam("userId") Long userId, Model model) {
    		log.info("request friend id: " + userId);
    
    		userService.addFriend(userId);
    		return "redirect:/";
    	}
    
    	@PostMapping("/friend/remove")
    	String removeFriend(@RequestParam("userId") Long userId, Model model) {
    		log.info("remove friend id: " + userId);
    
    		userService.removeFriend(userId);
    		return "redirect:/";
    	}
    }
    
    
  9. เพิ่ม FriendForm
    package com.magicalcyber.myfacebook.web.form;
    
    import org.hibernate.validator.constraints.NotEmpty;
    
    public class FriendForm {
    
    	private Long userId;
    
    	@NotEmpty
    	private String keyword;
    
    	public String getKeyword() {
    		return keyword;
    	}
    
    	public void setKeyword(String keyword) {
    		this.keyword = keyword;
    	}
    
    	public Long getUserId() {
    		return userId;
    	}
    
    	public void setUserId(Long userId) {
    		this.userId = userId;
    	}
    
    }
    
    
  10. เพิ่ม friend.jsp สำหรับหน้าค้นหาเพื่อน

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8"%>
    
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@taglib prefix="sec"
    	uri="http://www.springframework.org/security/tags"%>
    <%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>My Facebook</title>
    <link rel="stylesheet" type="text/css"
    	href='<c:url value="/css/bootstrap.min.css" />' />
    <link rel="stylesheet" type="text/css"
    	href='<c:url value="/css/main.css" />' />
    </head>
    <body>
    	<%@include file="nav.jsp"%>
    
    
    	<div class="container">
    
    		<div class="row">
    			<div class="col-md-4 col-md-offset-4 block">
    				<form:form servletRelativeAction="/friend/search" method="get"
    					modelAttribute="friendForm" cssClass="form-inline">
    					<form:hidden path="userId" />
    					<div class="form-group">
    						<form:input cssClass="form-control" path="keyword"
    							placeholder="Name or Email" />
    						<input type="submit" class="btn btn-primary" value="Search" />
    					</div>
    
    				</form:form>
    			</div>
    		</div>
    		<div class="row">
    			<div class="col-md-4 col-md-offset-4 block">
    				<c:choose>
    					<c:when test="${ not empty users }">
    						<div class="row">
    							<div class="col-sm-12">Found ${ users.size() }</div>
    						</div>
    						<hr />
    						<div class="row">
    							<c:forEach items="${ users }" var="user">
    								<div class="col-sm-8">
    									<div class="form-group">
    										<a href="#">${user.name }</a>
    									</div>
    								</div>
    								<div class="col-sm-4">
    									<c:choose>
    										<c:when test="${ user.friend }">
    											<form action='<c:url value="/friend/remove" />' method="post" >
    												<input type="hidden" name="userId" value="${ user.userId }" />
    												<input type='hidden' name='${_csrf.parameterName}'
    													value='${_csrf.token}' />
    												<input type="submit" class="btn btn-danger" value="Unfriend" />
    											</form>
    										</c:when>
    										<c:otherwise>
    											<form action='<c:url value="/friend/add" />' method="post">
    												<input type="hidden" name="userId" value="${ user.userId }" />
    												<input type='hidden' name='${_csrf.parameterName}'
    													value='${_csrf.token}' />
    												<input type="submit" class="btn btn-success"
    													value="Add friend" />
    											</form>
    										</c:otherwise>
    									</c:choose>
    								</div>
    							</c:forEach>
    						</div>
    					</c:when>
    					<c:otherwise>Not found a user</c:otherwise>
    				</c:choose>
    			</div>
    		</div>
    
    
    	</div>
    	<%@include file="footer.jsp"%>
    </body>
    </html>
    
  11. แก้ไข nav.jsp เพิ่มเมนูค้นหาเพื่อน

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8"%>
    <%@taglib prefix="sec"
    	uri="http://www.springframework.org/security/tags"%>
    <%@taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta name="viewport"
    	content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>My Facebook</title>
    </head>
    <body>
    	<nav class="navbar navbar-default navbar-static-top">
    		<div class="container-fluid">
    
    			<!-- Brand and toggle get grouped for better mobile display -->
    			<div class="navbar-header">
    				<button type="button" class="navbar-toggle collapsed"
    					data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"
    					aria-expanded="false">
    					<span class="sr-only">Toggle navigation</span>
    					<span class="icon-bar"></span>
    					<span class="icon-bar"></span>
    					<span class="icon-bar"></span>
    				</button>
    				<a class="navbar-brand" href="#">My Facebook</a>
    			</div>
    
    
    			<!-- Collect the nav links, forms, and other content for toggling -->
    			<div class="collapse navbar-collapse"
    				id="bs-example-navbar-collapse-1">
    				<ul class="nav navbar-nav">
    					<li class="active">
    						<a href='<c:url value="/" />'>Home <span class="sr-only">(current)</span></a>
    					</li>
    					<sec:authorize access="isAnonymous()">
    						<li>
    							<a href='<c:url value="/login" />'>Login</a>
    						</li>
    					</sec:authorize>
    					<sec:authorize access="isAuthenticated()">
    						<li>
    							<a href='<c:url value="/friend" />'> <span
    									class="glyphicon glyphicon glyphicon-user" aria-hidden="true">
    									<c:if test="${ not empty friendsWaitForApprove }">${ friendsWaitForApprove.size() }</c:if>
    								</span>
    							</a>
    						</li>
    						<li>
    							<a href="#"> <sec:authorize access="isAuthenticated()">
    									<sec:authentication var="user" property="principal" />
    									<c:out value="${ user.fullname }"></c:out>
    
    								</sec:authorize>
    							</a>
    						</li>
    
    						<li>
    
    							<form:form cssClass="navbar-form" servletRelativeAction="/logout"
    								method="post">
    								<input type="submit" class="btn btn-danger" value="Logout" />
    							</form:form>
    						</li>
    					</sec:authorize>
    				</ul>
    			</div>
    
    		</div>
    	</nav>
    </body>
    </html>
    

    เนื่องจากผมเรียกใช้ font ของ bootstrap ดังนั้นต้องเพิ่ม font ของ bootstrap เข้ามาด้วย (หาได้จาก zip ของ bootstrap)

    1-font

จากนั้นก็ run แล้วสมัครมาสอง account เมื่อ login เข้ามาแล้วทำการค้นหาเพื่อนอีกคน หากยังไม่เคยกด add ก็จะมีปุ่มให้ add ถ้าเคย add แล้วก็จะเป็นปุ่ม Unfriend ตามรูป
2-search-friend

เมื่อเป็น friend กันแล้วก็จะสามารถเห็น post ของอีกฝ่าย และสามารถเข้าไป comment ได้
2-post-comment

Test

ดาวโหลด code มาดูได้เลยครับ

Sourcecode

https://github.com/magickiat/myfacebook.git

Advertisements

5 thoughts on “Spring boot ตอนที่ 3 – กรณีศึกษา MyFacebook web (4)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s