<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.objects.Result;

@SuppressWarnings("serial")
@WebServlet(name = "HomePageScroll", value = "/HomePageScroll")
public class HomePageScroll extends HttpServlet {

	@Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    }
    
	private static final Logger logger = Logger.getLogger(HomePageScroll.class.getName());

    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    	
        PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
	    String startCursor = request.getParameter("cursor");
	    List<Post> posts = null;
		String endCursor = null;
					
	    try {
	      Result<Post> result = dao.listPosts(startCursor);
	      logger.info("ViewAlbum result = " + result);
	      posts = result.result;
	      logger.info("ViewAlbum posts = " + posts);
	      endCursor = result.cursor;
	    } catch (Exception e) {
	      throw new ServletException("Error listing posts", e);
	    }
	  
	    PrintWriter out = response.getWriter();
	    StringBuilder postNames = new StringBuilder();

	    for (Post post : posts) {
	      postNames.append(post.getTitle() + " ");
	      if (post.getInfo().length() > 200) {
	    	  StringBuilder str = new StringBuilder(post.getInfo());
	    	  str.setLength(200);
	    	  str.append(" ...");
	    	  post.setInfo(str.toString());
	      }
	      String resp = "";
          	
	      switch(post.getType()) {
	      
	      case "Photo":
	    	  resp += "				<a href='/ViewPost?id="+post.getId()+"&type="+post.getType()+"'>"
	    	  		+ "					<div class='col-sm-12 media-body'>"
	    	  		+ "			            <div class='col-sm-3 thumb'>"
	    	  		+ "			            	<img class='thumb' alt='ahhh' src='"+post.getImageUrl()+"'>"
	    	  		+ "			            </div>"
	    	  		+ "			            <div class='col-sm-1'>"
	    	  		+ "		                  <p class='post-description'><span><img class='icon' src='images/photo.svg' width='16'></span> "+post.getType()+"</p>"
	    	  		+ "		                </div>"
	    	  		+ "		              	<div class='col-sm-8'>"
	    	  		+ "		                  <h4 class='post-title'>"+post.getTitle()+"</h4>"
	    	  		+ "		                </div>"
	    	  		+ "		                <div class='col-sm-9'>"
	    	  		+ "		                  <p class='post-description'>"+post.getInfo()+"</p>"
	    	  		+ "		              	</div>"
	    	  		+ "		              	<div class='col-sm-12'>"
	    	  		+ "		              		<p class='post-description'>Added by "+post.getCreatedBy()+"</p>"
	    	  		+ "		              	</div>"
	    	  		+ "		            </div>"
	    	  		+ "				</a> ";
	    	  break;
	      case "Event":
	    	  resp += "				<a href='/ViewPost?id="+post.getId()+"&type="+post.getType()+"'>"
	    	  		+ "					<div class='col-sm-12 media-body'>"
	    	  		+ "	                  <div class='col-sm-3 thumb'>"
	    	  		+ "	                  	<img class='thumb' alt='ahhh' src='"+post.getImageUrl()+"'>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-1'>"
	    	  		+ "	                  	<p class='post-description'><span><img class='icon' src='images/event.svg' width='16'></span> "+post.getType()+"</p>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-4'>"
	    	  		+ "	                  	<h4 class='post-title'>"+post.getTitle()+"</h4>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-2'>"
	    	  		+ "	                  	<p class='post-description'>"+post.getDateString()+"</p>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-2'>"
	    	  		+ "	                  	<p class='post-description'>"+post.getTime()+"</p>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-9'>"
	    	  		+ "	                  	<p class='post-description'>"+post.getInfo()+"</p>"
	    	  		+ "	                  </div>"
	    	  		+ "	                  <div class='col-sm-12'>"
	    	  		+ "		              	<p class='post-description'>Added by "+post.getCreatedBy()+"</p>"
	    	  		+ "		              </div>"
	    	  		+ "	                </div>"
	    	  		+ "				</a> ";
	    	  break;
	      case "Video":
	    	  resp += "				<a href='/ViewPost?id="+post.getId()+"&type="+post.getType()+"'>"
	    	  		+ "					<div class='col-sm-12 media-body'>"
	    	  		+ "			            <div class='col-sm-3 thumb'>"
	    	  		+ "			            	<img class='thumb' alt='ahhh' src='"+post.getImageUrl()+"'>"
	    	  		+ "			            </div>"
	    	  		+ "			            <div class='col-sm-1'>"
	    	  		+ "		                  <p class='post-description'><span><img class='icon' src='images/video.svg' width='16'></span> "+post.getType()+"</p>"
	    	  		+ "		                </div>"
	    	  		+ "		              	<div class='col-sm-8'>"
	    	  		+ "		                  <h4 class='post-title'>"+post.getTitle()+"</h4>"
	    	  		+ "		                </div>"
	    	  		+ "		                <div class='col-sm-9'>"
	    	  		+ "		                  <p class='post-description'>"+post.getInfo()+"</p>"
	    	  		+ "		              	</div>"
	    	  		+ "		              	<div class='col-sm-12'>"
	    	  		+ "		              		<p class='post-description'>Added by "+post.getCreatedBy()+"</p>"
	    	  		+ "		              	</div>"
	    	  		+ "		            </div>"
	    	  		+ "				</a> ";
	    	  break;
	      }
          
          out.write(resp);
	    }
	    out.write("<input hidden='hidden' id='cursor' value='"+endCursor+"'></input>");
	    out.close();
    }
}
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.AlbumDao;
import com.boulder.mchistory.objects.Album;
import com.boulder.mchistory.objects.Result;

@SuppressWarnings("serial")
@WebServlet(name = "ListAlbumsScroll", value = "/ListAlbumsScroll")
public class ListAlbumsScroll extends HttpServlet {

	@Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    }
    
	private static final Logger logger = Logger.getLogger(ListAlbumsScroll.class.getName());

    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    	
        AlbumDao dao = (AlbumDao) this.getServletContext().getAttribute("albumDao");
	    String startCursor = request.getParameter("cursor");
	    List<Album> albums = null;
		String endCursor = null;
					
	    try {
	      Result<Album> result = dao.listAlbums(startCursor);
	      albums = result.result;
	      endCursor = result.cursor;
	    } catch (Exception e) {
	      throw new ServletException("Error listing albums", e);
	    }
	  
	    PrintWriter out = response.getWriter();
	    StringBuilder albumNames = new StringBuilder();
	    
	    for (Album album : albums) {
	      albumNames.append(album.getTitle() + " ");
	      String resp = "";
          	
              resp += "<div class='media'>"
              			+ "<a href='/ViewPost?id="+album.getId()+"&type=Album'>"
              				+ "<div class='media-left'>"
              					+ "<h4 class='post-title'>"+album.getTitle()+"</h4>"
              					+ "<img class='thumb' alt='ahhh' src='"+album.getImageUrl()+"'>"
              				+ "</div>"
              			+ "</a>";
              				
              if (request.getSession().getAttribute("isAdmin") != null) {
            	  Boolean isAdmin = Boolean.valueOf((String)request.getSession().getAttribute("isAdmin"));
            	  if (isAdmin) {
            	  resp += "<div class='btn-group'>"
        					+ "<a href='/EditPost?id="+album.getId()+"&type=Album&album=${album.id}' class='btn btn-default btn-xs'>"
      							+ "<span class='edit-16 icon'></span> Edit"
      						+ "</a>"
      						+ "<a href='/DeletePost?id="+album.getId()+"&type=Album&album=${album.id}' class='btn btn-danger btn-xs'>"
      							+ "<span class='delete-16 icon'></span> Delete"
      						+ "</a>"
      					+ "</div>";
            	  }
              }
              resp += "</div>";
          out.write(resp);
	    }
	    out.write("<input hidden='hidden' id='cursor' value='"+endCursor+"'></input>");
	    out.close();
    }
}
<file_sep>package com.boulder.mchistory.auth;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

// [START example]
@WebServlet(name = "Logout", value = "/Logout")
@SuppressWarnings("serial")
public class LogoutServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {
    // you can also make an authenticated request to logout, but here we choose to
    // simply delete the session variables for simplicity
    HttpSession session =  req.getSession(false);
    if (session != null) {
      session.invalidate();
    }
    // rebuild session
    req.getSession();
    String url = resp.encodeRedirectURL("Home");
	resp.sendRedirect(url);
  }
}
// [END example]
<file_sep><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.boulder</groupId>
    <artifactId>mchistory-ubm</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>MC History</name>
    <url>http://maven.apache.org</url>
    <dependencies>

        <!-- jUnit -->
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

        <!-- MongoDB -->
        <!-- https://mvnrepository.com/artifact/org.mongodb/mongodb-driver -->
        <dependency>
            <groupId>org.mongodb</groupId>
            <artifactId>mongodb-driver</artifactId>
            <version>3.11.0</version>
        </dependency>

        <!-- Google GSON -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

        <!-- Google Datastore -->
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>google-cloud-datastore</artifactId>
            <version>1.88.0</version>
        </dependency>

        <!-- JSP Taglibs -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>taglibs</groupId>
            <artifactId>standard</artifactId>
            <version>1.1.2</version>
        </dependency>
        <!-- /JSP Taglibs -->

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <!-- Google Storage -->
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>google-cloud-storage</artifactId>
            <version>1.88.0</version>
        </dependency>

        <!-- Mailgun -->
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.6.2</version>
        </dependency>

        <dependency>
            <groupId>com.mashape.unirest</groupId>
            <artifactId>unirest-java</artifactId>
            <version>1.4.9</version>
        </dependency>

        <dependency>
            <groupId>net.sargue</groupId>
            <artifactId>mailgun</artifactId>
            <version>1.9.0</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>1.19.4</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.19.4</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-multipart</artifactId>
            <version>1.19.4</version>
        </dependency>

        <!-- Guava declaration needed to avoid dependency collision in GCP APIs -->
        <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.0-jre</version>
        </dependency>

        <!-- IO Utils: used in CloudStorageHelper -->
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>

        <!-- Google Sign-In -->
        <!-- https://mvnrepository.com/artifact/com.google.apis/google-api-services-identitytoolkit -->
        <dependency>
            <groupId>com.google.apis</groupId>
            <artifactId>google-api-services-identitytoolkit</artifactId>
            <version>v3-rev20180723-1.30.1</version>
        </dependency>
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.9.2</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>MC-History</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.objects.Result;

@SuppressWarnings("serial")
@MultipartConfig
@WebServlet(name = "Events", value = "/Events")
public class Events extends HttpServlet {
	
	private final Logger logger = Logger.getLogger(Events.class.getName());

	@Override
	  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
	      IOException {
		
		PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
	    String startCursor = req.getParameter("cursor");
	    List<Post> events = null;
		String endCursor = null;
		
		try {
		      Result<Post> result = dao.listEvents(startCursor);
		      events = result.result;
		      endCursor = result.cursor;
		    } catch (Exception e) {
		      throw new ServletException("Error listing photos", e);
		    }
		
		StringBuilder eventNames = new StringBuilder();
	    for (Post event : events) {
	      eventNames.append(event.getTitle() + " ");
	      if (event.getInfo().length() > 200) {
	    	  StringBuilder str = new StringBuilder(event.getInfo());
	    	  str.setLength(200);
	    	  str.append(" ...");
	    	  event.setInfo(str.toString());
	      }
		}
	    
	    req.setAttribute("events", events);
		req.setAttribute("cursor", endCursor);
		req.setAttribute("page", "events");
		req.getRequestDispatcher("/base.jsp").forward(req, resp);
		
	}
	
	@Override
	  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
	      IOException {
		
		
		
	}

}
<file_sep>package com.boulder.mchistory.daos;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.boulder.mchistory.objects.Album;
import com.boulder.mchistory.objects.Result;
import com.google.cloud.datastore.Cursor;
import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.FullEntity;
import com.google.cloud.datastore.IncompleteKey;
import com.google.cloud.datastore.Key;
import com.google.cloud.datastore.KeyFactory;
import com.google.cloud.datastore.Query;
import com.google.cloud.datastore.QueryResults;
import com.google.cloud.datastore.StructuredQuery.OrderBy;

public class AlbumDatastore implements AlbumDao {
	
	 // [START constructor]
	  private Datastore datastore;
	  private KeyFactory keyFactory;
	 

	public AlbumDatastore() {
		datastore = DatastoreOptions.getDefaultInstance().getService(); // Authorized Datastore service
	    keyFactory = datastore.newKeyFactory().setKind("Album");      // Is used for creating keys later
	}
	 // [END constructor]
	// [START entityToAlbum]
	public Album entityToAlbum(Entity entity) {
	    return new Album.Builder()                                     // Convert to Album form
	        .id(entity.getKey().getId())
	        .title(entity.getString(Album.TITLE))
	        .imageUrl(entity.contains(Album.IMAGE_URL) ? entity.getString(Album.IMAGE_URL) : null)
	        .createdBy(entity.getString(Album.CREATED_BY))
	        .createdById(entity.getString(Album.CREATED_BY_ID))
	        .build();
	  }
	  // [END entityToAlbum]
	@Override
	public Long createAlbum(Album album) throws SQLException {
		IncompleteKey key = keyFactory.newKey();          // Key will be assigned once written
	    FullEntity<IncompleteKey> incAlbumEntity = Entity.newBuilder(key)  // Create the Entity
	        .set(Album.TITLE, album.getTitle())
	        .set(Album.IMAGE_URL, album.getImageUrl())
	        .set(Album.CREATED_BY, album.getCreatedBy())
	        .set(Album.CREATED_BY_ID, album.getCreatedById())
	        .build();
	    Entity albumEntity = datastore.add(incAlbumEntity); // Save the Entity
	    return albumEntity.getKey().getId();                     // The ID of the Key
	}
	@Override
	public void updateAlbum(Album album) throws SQLException {
		 Key key = keyFactory.newKey(album.getId());  // From a album, create a Key
		    Entity entity = Entity.newBuilder(key)         // Convert Album to an Entity
		        .set(Album.TITLE, album.getTitle())
		        .set(Album.IMAGE_URL, album.getImageUrl())
		        .set(Album.CREATED_BY, album.getCreatedBy())
		        .set(Album.CREATED_BY_ID, album.getCreatedById())
		        .build();
		    datastore.update(entity);                   // Update the Entity
	}
	@Override
	public void deleteAlbum(Long albumId) throws SQLException {
		Key key = keyFactory.newKey(albumId);        // Create the Key
	    datastore.delete(key);                      // Delete the Entity
	}
	 // [START entitiesToAlbums]
	  public List<Album> entitiesToAlbums(QueryResults<Entity> resultList) {
	    List<Album> resultAlbums = new ArrayList<>();
	    while (resultList.hasNext()) {  // We still have data
	      resultAlbums.add(entityToAlbum(resultList.next()));      // Add the Album to the List
	    }
	    return resultAlbums;
	  }
	  // [END entitiesToAlbums]
	// [START read]
	  @Override
	  public Album viewAlbum(Long albumId) {
	    Entity albumEntity = datastore.get(keyFactory.newKey(albumId)); // Load an Entity for Key(id)
	    return entityToAlbum(albumEntity);
	  }
	  // [END read]
	@Override
	public Result<Album> listAlbums(String startCursorString) throws SQLException {
		Cursor startCursor = null;
	    if (startCursorString != null && !startCursorString.equals("")) {
	      startCursor = Cursor.fromUrlSafe(startCursorString);    // Where we left off
	    }
	    Query<Entity> query = Query.newEntityQueryBuilder()       // Build the Query
	        .setKind("Album")                                     // We only care about Albums
	        .setLimit(10)                                         // Only show 10 at a time
	        .setStartCursor(startCursor)                          // Where we left off
	        .setOrderBy(OrderBy.asc(Album.TITLE))                  // Use default Index "title"
	        .build();
	    QueryResults<Entity> resultList = datastore.run(query);   // Run the query
	    List<Album> resultAlbums = entitiesToAlbums(resultList);     // Retrieve and convert Entities
	    Cursor cursor = resultList.getCursorAfter();              // Where to start next time
	    if (cursor != null && resultAlbums.size() == 10) {         // Are we paging? Save Cursor
	      String cursorString = cursor.toUrlSafe();               // Cursors are WebSafe
	      return new Result<>(resultAlbums, cursorString);
	    } else {
	      return new Result<>(resultAlbums);
	    }
	}


}
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.objects.Result;

@SuppressWarnings("serial")
@WebServlet(name = "VideosScroll", value = "/VideosScroll")
public class VideosScroll extends HttpServlet {

	@Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    }
    
	private static final Logger logger = Logger.getLogger(VideosScroll.class.getName());

    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    	
        PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
	    String startCursor = request.getParameter("cursor");
	    List<Post> videos = null;
		String endCursor = null;
					
	    try {
	      Result<Post> result = dao.listVideos(startCursor);
	      videos = result.result;
	      endCursor = result.cursor;
	    } catch (Exception e) {
	      throw new ServletException("Error listing videos", e);
	    }
	  
	    PrintWriter out = response.getWriter();
	    StringBuilder videoNames = new StringBuilder();
	    
	    for (Post video : videos) {
	      videoNames.append(video.getTitle() + " ");
	      if (video.getInfo().length() > 200) {
	    	  StringBuilder str = new StringBuilder(video.getInfo());
	    	  str.setLength(200);
	    	  str.append(" ...");
	    	  video.setInfo(str.toString());
	      }
	      String resp = "";
          	
              resp += "<div class='media'>"
              			+ "<a href='/ViewPost?id="+video.getId()+"&type=Video'>"
              				+ "<div class='media-left'>"
              					+ "<h4 class='post-title'>"+video.getTitle()+"</h4>"
              					+ "<img class='thumb' alt='ahhh' src='"+video.getImageUrl()+"'>"
              					+ "<p class='post-description'>"+video.getInfo()+"</p>"
              				+ "</div>"
              			+ "</a>";
              				
              if (request.getSession().getAttribute("isAdmin") != null) {
            	  Boolean isAdmin = Boolean.valueOf((String)request.getSession().getAttribute("isAdmin"));
            	  if (isAdmin) {
	            	  resp += "<div class='btn-group'>"
	        					+ "<a href='/EditPost?id="+video.getId()+"&type=Video&album=${album.id}' class='btn btn-default btn-xs'>"
	      							+ "<span class='edit-16 icon'></span> Edit"
	      						+ "</a>"
	      						+ "<a href='/DeletePost?id="+video.getId()+"&type=Video&album=${album.id}' class='btn btn-danger btn-xs'>"
	      							+ "<span class='delete-16 icon'></span> Delete"
	      						+ "</a>"
	      					+ "</div>";
	              }
              }
              resp += "</div>";
          out.write(resp);
	    }
	    out.write("<input hidden='hidden' id='cursor' value='"+endCursor+"'></input>");
	    out.close();
    }
}
<file_sep>package com.boulder.mchistory.daos;

import java.sql.SQLException;
import java.util.logging.Logger;

import javax.servlet.http.HttpSession;

import com.boulder.mchistory.objects.User;
import com.google.cloud.datastore.Datastore;
import com.google.cloud.datastore.DatastoreOptions;
import com.google.cloud.datastore.Entity;
import com.google.cloud.datastore.EntityQuery;
import com.google.cloud.datastore.KeyFactory;
import com.google.cloud.datastore.Query;
import com.google.cloud.datastore.QueryResults;
import com.google.cloud.datastore.StructuredQuery.PropertyFilter;

public class SessionDatastore implements SessionDao {
	
	private static final Logger logger = Logger.getLogger(UserDatastore.class.getName());
	private Datastore datastore;
	private KeyFactory keyFactory;

	public SessionDatastore() {
		datastore = DatastoreOptions.getDefaultInstance().getService(); // Authorized Datastore service
	    keyFactory = datastore.newKeyFactory().setKind("SessionVariable");
	}

	@Override
	public String getSessionId(Long uid) throws SQLException {
		String id = null;
		EntityQuery query = Query.newEntityQueryBuilder()          // Build the Query
				.setKind("SessionVariable")                                        // We only care about Books
				.setFilter(PropertyFilter.eq("userId", uid))// Only for this user
				.build();
		QueryResults<Entity> result = datastore.run(query);
		if (result.hasNext()) {
			id = result.next().getKey().getId().toString();
		}
		return id;
	}

}
<file_sep>package com.boulder.mchistory.basic;

import com.boulder.mchistory.daos.AlbumDao;
import com.boulder.mchistory.objects.Album;
import com.boulder.mchistory.util.CloudStorageHelper;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

// [START example]
@SuppressWarnings("serial")
@MultipartConfig
@WebServlet(name = "EditAlbum", value = "/EditAlbum")
public class EditAlbum extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    AlbumDao dao = (AlbumDao) this.getServletContext().getAttribute("albumDao");
    try {
      Album album = dao.viewAlbum(Long.decode(req.getParameter("id")));
      req.setAttribute("album", album);
      req.setAttribute("action", "Edit");
      req.setAttribute("destination", "EditAlbum");
      req.setAttribute("page", "form-album");
      req.getRequestDispatcher("/base.jsp").forward(req, resp);
    } catch (Exception e) {
      throw new ServletException("Error loading album for editing", e);
    }
  }

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    CloudStorageHelper storageHelper =
        (CloudStorageHelper) req.getServletContext().getAttribute("storageHelper");
    Part part = req.getPart("file");
    String imageUrl =
        storageHelper.getImageUrl(part, 
            req, resp, getServletContext().getInitParameter("skelly.bucket"));
    AlbumDao dao = (AlbumDao) this.getServletContext().getAttribute("albumDao");
    try {

      Album oldAlbum = dao.viewAlbum(Long.decode(req.getParameter("id")));

      Album album = new Album.Builder()
          .createdBy(oldAlbum.getCreatedBy())
          .createdById(oldAlbum.getCreatedById())
          .id(Long.decode(req.getParameter("id")))
          .title(req.getParameter("title"))
          .imageUrl(imageUrl)
          .build();
      dao.updateAlbum(album);
      String url = resp.encodeRedirectURL("ViewAlbum?id=" + req.getParameter("id"));
      resp.sendRedirect(url);
    } catch (Exception e) {
      throw new ServletException("Error updating album", e);
    }
  }
}
// [END example]
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.objects.Result;

@SuppressWarnings("serial")
@WebServlet(name = "EventsScroll", value = "/EventsScroll")
public class EventsScroll extends HttpServlet {

	@Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    }
    
	private static final Logger logger = Logger.getLogger(EventsScroll.class.getName());

    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
    	
        PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
	    String startCursor = request.getParameter("cursor");
	    List<Post> events = null;
		String endCursor = null;
					
	    try {
	      Result<Post> result = dao.listEvents(startCursor);
	      events = result.result;
	      endCursor = result.cursor;
	    } catch (Exception e) {
	      throw new ServletException("Error listing events", e);
	    }
	  
	    PrintWriter out = response.getWriter();
	    StringBuilder eventNames = new StringBuilder();
	    
	    for (Post event : events) {
	      eventNames.append(event.getTitle() + " ");
	      if (event.getInfo().length() > 200) {
	    	  StringBuilder str = new StringBuilder(event.getInfo());
	    	  str.setLength(200);
	    	  str.append(" ...");
	    	  event.setInfo(str.toString());
	      }
	      
	      String resp = 			"<div class='media'>"
              				+ "				<a href='/ViewPost?id="+event.getId()+"&type=Event'>"
              				+ "				   <div class='col-sm-12 media-body'>"
              				+ "                  <div class='col-sm-3 thumb'>"
              				+ "                  	<img class='thumb' alt='ahhh' src='"+event.getImageUrl()+"'>"
              				+ "                  </div>"
              				+ "                  <div class='col-sm-5'>"
              				+ "                  	<h4 class='post-title'>"+event.getTitle()+"</h4>"
              				+ "                  </div>"
              				+ "                  <div class='col-sm-2'>"
              				+ "                  	<p class='post-description'>"+event.getDateString()+"</p>"
              				+ "                  </div>"
              				+ "                  <div class='col-sm-2'>"
              				+ "                  	<p class='post-description'>"+event.getTime()+"</p>"
              				+ "                  </div>"
              				+ "                  <div class='col-sm-9'>"
              				+ "                  	<p class='post-description'>"+event.getInfo()+"</p>"
              				+ "                  </div>"
              				+ "                </div>"
              				+ "				</a>";
	      
	      if (request.getSession().getAttribute("isAdmin") != null) {
        	  Boolean isAdmin = Boolean.valueOf((String)request.getSession().getAttribute("isAdmin"));
        	  if (isAdmin) {
	            	  resp += "<div class='btn-group'>"
	        					+ "<a href='/EditPost?id="+event.getId()+"&type=Event&album=${album.id}' class='btn btn-default btn-xs'>"
	      						+ "<span class='edit-16 icon'></span> Edit"
	      					+ "</a>"
	      					+ "<a href='/DeletePost?id="+event.getId()+"&type=Event&album=${album.id}' class='btn btn-danger btn-xs'>"
	      						+ "<span class='delete-16 icon'></span> Delete"
	      					+ "</a>"
	      				+ "</div>";
	              }
	      	}
              resp += "</div>";
          out.write(resp);
	    }
	    out.write("<input hidden='hidden' id='cursor' value='"+endCursor+"'></input>");
	    out.close();
    }
}
<file_sep>package com.boulder.mchistory.basic;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.util.CloudStorageHelper;
import com.google.cloud.Timestamp;

import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@SuppressWarnings("serial")
@MultipartConfig
@WebServlet(name = "BulkUpload", urlPatterns = {"/BulkUpload"})
public class BulkUpload extends HttpServlet {

  private static final Logger logger = Logger.getLogger(BulkUpload.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
	
	switch (req.getParameter("type")) {
		
	case "Photo":
		String albumId = req.getParameter("album");
		req.setAttribute("album", albumId);
	    req.setAttribute("action", "Bulk Upload");
	    req.setAttribute("type", "Photo");
	    req.setAttribute("destination", "BulkUpload");
	    req.setAttribute("page", "bulk-upload");
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	    break;
	case "Video":
	    req.setAttribute("action", "Bulk Upload");
	    req.setAttribute("type", "Video");
	    req.setAttribute("destination", "BulkUpload");
	    req.setAttribute("page", "bulk-upload");
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	    break;
	} 
  }
  // [END setup]

  // [START formpost]
  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
	  
	CloudStorageHelper storageHelper =
		(CloudStorageHelper) req.getServletContext().getAttribute("storageHelper");
	String imageUrl = null;
    String videoUrl = null;
    Part part1 = req.getPart("file");

    String createdByString = "";
    String createdByIdString = "";
    if (req.getSession().getAttribute("token") != null) { // Does the user have a logged in session?
      createdByString = (String) req.getSession().getAttribute("userName");
      createdByIdString = (String) req.getSession().getAttribute("userId");
    }

    PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
    
    List<Part> fileParts = req.getParts().stream().filter(
    	part -> "file".equals(part.getName())).collect(Collectors.toList());
    
    switch (req.getParameter("type")) {
    
    case "Photo":
    	String album = req.getParameter("album");
    	for (Part part : fileParts) {
            imageUrl = storageHelper.getImageUrl(part, req, resp, getServletContext().getInitParameter("skelly.bucket"));
            Post photo = new Post.Builder()
    			.type("Photo")
    			.album(album)
    			.dateCreated(Timestamp.of(new Date()))
    			.title("")
    			.imageUrl(imageUrl)
    			.info("")
    	        .createdBy(createdByString)
    	        .createdById(createdByIdString)
    	        .build();
        	    try {
        	      dao.createPost(photo);
        	      logger.log(Level.INFO, "Bulk Upload Complete", photo);
        	    } catch (Exception e) {
        	      throw new ServletException("Error uploading photos", e);
        	    }
        }
    	String photosUrl = resp.encodeRedirectURL("ViewAlbum?album="+album);
    	resp.sendRedirect(photosUrl);
    	break;
    	
    case "Video":
    	for (Part part : fileParts) {
            videoUrl = storageHelper.getVideoUrl(part, req, resp, getServletContext().getInitParameter("skelly.bucket"));
            Post video = new Post.Builder()
        		.type("Video")
        		.dateCreated(Timestamp.of(new Date()))
        		.title("")
        		.imageUrl("images/logo1.png")
        		.videoUrl(videoUrl)
        		.info("")
                .createdBy(createdByString)
                .createdById(createdByIdString)
                .build();
            	
        	    try {
        	      dao.createPost(video);
        	      logger.log(Level.INFO, "Created photo {0}", video);
        	    } catch (Exception e) {
        	      throw new ServletException("Error creating video", e);
        	    }
        }
    	String videosUrl = resp.encodeRedirectURL("Videos");
		resp.sendRedirect(videosUrl);
    	break;
    } 

  }
  // [END formpost]
}
// [END example]
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.boulder.mchistory.daos.PostDao;

// [START example]
@SuppressWarnings("serial")
@WebServlet(name = "DeletePost", value = "/DeletePost")
public class DeletePost extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
	  
	PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
    Long id = Long.decode(req.getParameter("id"));
    String type = req.getParameter("type");
    
    try {
      dao.deletePost(id);
    } catch (Exception e) {
      throw new ServletException("Error deleting post", e);
    }
    
    String eventsUrl = resp.encodeRedirectURL("Events");
    String photosUrl = resp.encodeRedirectURL("ViewAlbum?album="+req.getParameter("album"));
    String videosUrl = resp.encodeRedirectURL("Videos");
    
      switch (type) {
      
      case "Event":
    	  resp.sendRedirect(eventsUrl);
    	  break;
      case "Photo":
    	  resp.sendRedirect(photosUrl);
    	  break;
      case "Video":
    	  resp.sendRedirect(videosUrl);
      }
    
  }
  
}
// [END example]
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// [START example]
@SuppressWarnings("serial")
@WebServlet(name = "ListBkmks", value = "/ListBkmks")
public class ListBkmks extends HttpServlet {

  private static final Logger logger = Logger.getLogger(ListBkmks.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException,
        ServletException {
	
	  //TODO: CONVERT ALL PHOTOS TO POSTS
	
  }
}
// [END example]
<file_sep>package com.boulder.mchistory.objects;

public class Text {

	String body;
	Long id;
	
	public static final String BODY = "body";
	public static final String ID = "id";
	
	public String getBody() {
		return body;
	}
	
	public void setBody(String body) {
		this.body = body;
	}
	
	public Long getId() {
		return id;
	}

}
<file_sep>package com.boulder.mchistory.basic;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

import com.boulder.mchistory.daos.AlbumDao;
import com.boulder.mchistory.objects.Album;
import com.boulder.mchistory.util.CloudStorageHelper;

@SuppressWarnings("serial")
//[START annotations]
@MultipartConfig
@WebServlet(name = "NewAlbum", urlPatterns = {"/NewAlbum"})
//[END annotations]
public class NewAlbum extends HttpServlet {
	
	private static final Logger logger = Logger.getLogger(NewAlbum.class.getName());

	 // [START setup]
	  @Override
	  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
	      IOException {
		  this.getServletContext().setAttribute("album", null);
	    req.setAttribute("action", "Add");          // Part of the Header in form.jsp
	    req.setAttribute("destination", "NewAlbum");  // The urlPattern to invoke (this Servlet)
	    req.setAttribute("page", "form-album");           // Tells base.jsp to include form.jsp
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	  }
	  // [END setup]

	  // [START formalbum]
	  @Override
	  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
	      IOException {
	    CloudStorageHelper storageHelper =
	        (CloudStorageHelper) req.getServletContext().getAttribute("storageHelper");
	    Part part = req.getPart("file");
	    String imageUrl =
	        storageHelper.getImageUrl(part, 
	            req, resp, getServletContext().getInitParameter("skelly.bucket"));

	    String createdByString = "";
	    String createdByIdString = "";
	    if (req.getSession().getAttribute("token") != null) { // Does the user have a logged in session?
	      createdByString = (String) req.getSession().getAttribute("userName");
	      createdByIdString = (String) req.getSession().getAttribute("userId");
	    }

	    AlbumDao dao = (AlbumDao) this.getServletContext().getAttribute("albumDao");

	    Album album = new Album.Builder()
	        .createdBy(createdByString)
	        .createdById(createdByIdString)
	        .title(req.getParameter("title"))
	        .imageUrl(imageUrl)
	        .build();
	    try {
	      Long id = dao.createAlbum(album);
	      this.getServletContext().setAttribute("album", id);
	      logger.log(Level.INFO, "Created album {0}", album);
	      String url = resp.encodeRedirectURL("ListAlbums");
	      resp.sendRedirect(url);
	    } catch (Exception e) {
	      throw new ServletException("Error creating album", e);
	    }
	  }
	  // [END formalbum]
}
<file_sep>package com.boulder.mchistory.basic;

import com.boulder.mchistory.daos.PostDao;
import com.boulder.mchistory.objects.Post;
import com.boulder.mchistory.util.CloudStorageHelper;
import com.google.cloud.Timestamp;

import java.io.IOException;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

// [START example]
@SuppressWarnings("serial")
// [START annotations]
@MultipartConfig
@WebServlet(name = "NewPost", urlPatterns = {"/NewPost"})
// [END annotations]
public class NewPost extends HttpServlet {

  private static final Logger logger = Logger.getLogger(NewPost.class.getName());

  // [START setup]
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
	
	switch (req.getParameter("type")) {
		
	case "Photo":
		String album = req.getParameter("id");
		req.setAttribute("album", album);
	    req.setAttribute("action", "New");
	    req.setAttribute("type", "Photo");
	    req.setAttribute("destination", "NewPost?type=Photo");
	    req.setAttribute("page", "form-post");
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	    break;
	case "Event":
	    req.setAttribute("action", "New");
	    req.setAttribute("type", "Event");
	    req.setAttribute("destination", "NewPost?type=Event");
	    req.setAttribute("page", "form-post");
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	    break;
	case "Video":
	    req.setAttribute("action", "New");
	    req.setAttribute("type", "Video");
	    req.setAttribute("destination", "NewPost?type=Video");
	    req.setAttribute("page", "form-post");
	    req.getRequestDispatcher("/base.jsp").forward(req, resp);
	    break;
	} 
  }
  // [END setup]

  // [START formpost]
  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
	  
	CloudStorageHelper storageHelper =
		(CloudStorageHelper) req.getServletContext().getAttribute("storageHelper");
	String bucket = getServletContext().getInitParameter("skelly.bucket");
	String imageUrl = null;
    String videoUrl = null;
    
    Part img = req.getPart("image");
    Part vid = req.getPart("video");
    
    String createdByString = (String) req.getSession().getAttribute("userName");
    String createdByIdString = (String) req.getSession().getAttribute("userId");
    

    PostDao dao = (PostDao) this.getServletContext().getAttribute("postDao");
    
    String text = req.getParameter("info");
    
    StringBuffer preBuff = new StringBuffer(text);
	 int preLoc = (new String(preBuff)).indexOf("<br>");
	    while(preLoc > 0){
	        preBuff.replace(preLoc, preLoc+4, "");
	        preLoc = (new String(preBuff)).indexOf("<br>");
	   }
	    
	    logger.info("Buffered preBuff: " + preBuff);
	
	StringBuffer buff = new StringBuffer(preBuff);
	 int loc = (new String(preBuff)).indexOf('\n');
	    while(loc > 0){
	        preBuff.replace(loc, loc+1, "<br>");
	        loc = (new String(preBuff)).indexOf('\n');
	   }
	    
	    logger.info("Buffered buff: " + buff);
	    
	String body = preBuff.toString();
    
    switch (req.getParameter("type")) {
    	
    case "Photo":
    	imageUrl =
    	storageHelper.getImageUrl(img, req, resp, bucket);
    	String album = req.getParameter("album");
    	Post photo = new Post.Builder()
    			.type("Photo")
    			.album(album)
    			.dateCreated(Timestamp.of(new Date()))
    			.title(req.getParameter("title"))
    			.imageUrl(imageUrl)
    			.info(body)
    	        .createdBy(createdByString)
    	        .createdById(createdByIdString)
    	        .build();
    	    try {
    	      dao.createPost(photo);
    	      logger.log(Level.INFO, "Created photo {0}", photo);
    	    } catch (Exception e) {
    	      throw new ServletException("Error creating photo", e);
    	    }

    	    String photosUrl = resp.encodeRedirectURL("ViewAlbum?album=" + album);
    	    resp.sendRedirect(photosUrl);
    	 break;
    	 
    case "Event":
    	imageUrl =
    	storageHelper.getImageUrl(img, req, resp, bucket);
    	DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US);
    	Date date = null;
    	try {
			date = format.parse(req.getParameter("date"));
		} catch (ParseException e1) {
			e1.printStackTrace();
		}
    	Post event = new Post.Builder()
    			.type("Event")
    			.date(Timestamp.of(date))
    			.dateString(req.getParameter("date"))
    			.time(req.getParameter("time"))
    			.location(req.getParameter("location"))
    			.dateCreated(Timestamp.of(new Date()))
    			.title(req.getParameter("title"))
    			.imageUrl(imageUrl)
    			.info(body)
    	        .createdBy(createdByString)
    	        .createdById(createdByIdString)
    	        .build();
    	    try {
    	      dao.createPost(event);
    	      logger.log(Level.INFO, "Created photo {0}", event);
    	    } catch (Exception e) {
    	      throw new ServletException("Error creating photo", e);
    	    }

    	    String eventsUrl = resp.encodeRedirectURL("Events");
    	    resp.sendRedirect(eventsUrl);
    	 break;
    	 
    case "Video":
    	imageUrl = storageHelper.getImageUrl(img, req, resp, bucket);
    	videoUrl = storageHelper.getVideoUrl(vid, req, resp, bucket);
    	
    	Post video = new Post.Builder()
		.type("Video")
		.dateCreated(Timestamp.of(new Date()))
		.title(req.getParameter("title"))
		.imageUrl(imageUrl)
		.videoUrl(videoUrl)
		.info(body)
        .createdBy(createdByString)
        .createdById(createdByIdString)
        .build();
    	
	    try {
	      dao.createPost(video);
	      logger.log(Level.INFO, "Created photo {0}", video);
	    } catch (Exception e) {
	      throw new ServletException("Error creating video", e);
	    }
	    
	    String videosUrl = resp.encodeRedirectURL("Videos");
	    resp.sendRedirect(videosUrl);
	    break;
	    }

  }
  // [END formpost]
}
// [END example]
<file_sep>package com.boulder.mchistory.basic;

import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.boulder.mchistory.auth.LoginEmail;


public class FieldValidator {
	
	private static final Logger logger = Logger.getLogger(LoginEmail.class.getName());


	private static final String EMAIL_PATTERN =
			"^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
			+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
	
	private static final String PASSWORD_PATTERN =
            "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})";

		public static boolean validEmail(String email) {
			Pattern pattern = Pattern.compile(EMAIL_PATTERN);
			Matcher matcher = pattern.matcher(email);
			return matcher.matches();
		}

		public static boolean validPass(String pass) {
			  Pattern pattern = Pattern.compile(PASSWORD_PATTERN);
			  Matcher matcher = pattern.matcher(pass);
			  return matcher.matches();
		  }


}
<file_sep>package com.boulder.mchistory.daos;

import com.boulder.mchistory.auth.PasswordStorage;
import com.boulder.mchistory.auth.PasswordStorage.CannotPerformOperationException;
import com.boulder.mchistory.auth.PasswordStorage.InvalidHashException;
import com.boulder.mchistory.objects.Result;
import com.boulder.mchistory.objects.User;
import com.google.cloud.datastore.*;
import com.google.cloud.datastore.StructuredQuery.PropertyFilter;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class UserDatastore implements UserDao {

	private final Datastore datastore;
	private final KeyFactory keyFactory;
	private final KeyFactory hashFactory;

	public UserDatastore() {
		datastore = DatastoreOptions.getDefaultInstance().getService();
	    keyFactory = datastore.newKeyFactory().setKind("User");
	    hashFactory = datastore.newKeyFactory().setKind("Hash");
	}
	
	@Override
	public Boolean isUser(String email) {
		boolean isUser = false;
		EntityQuery query = Query.newEntityQueryBuilder()
				.setKind("User")
				.setFilter(PropertyFilter.eq(User.EMAIL, email))
				.build();
		QueryResults<Entity> result = datastore.run(query);
		
		if (result.hasNext()) {
			isUser = true;
		}
		return isUser;
	}
	
	public Long getUid(String email) throws SQLException {
		Long resp;

		EntityQuery query = Query.newEntityQueryBuilder()
				.setKind("User")
				.setFilter(PropertyFilter.eq(User.EMAIL, email))
				.build();
		QueryResults<Entity> result = datastore.run(query);

		if (result.hasNext()) {
			resp = result.next().getKey().getId();
		} else {
			throw new SQLException("User not found.");
		}

		return resp;
	}
	
	public User entityToUser(Entity entity) {
	    return new User.Builder()
	        .email(entity.getString(User.EMAIL))
	        .username(entity.getString(User.USERNAME))
	    	.emailVerified(entity.getBoolean(User.EMAILVERIFIED))
	    	.isAdmin(entity.getBoolean(User.ISADMIN))
	        .id(entity.getKey().getId())
	        .imageUrl(entity.contains(User.IMAGE_URL) ? entity.getString(User.IMAGE_URL) : null)
	        .role(entity.getString(User.ROLE))
	        .build();
	  }
	
	public List<User> entitiesToUsers(QueryResults<Entity> results) {
		List<User> resultList = new ArrayList<>();
		while (results.hasNext()) {
		      resultList.add(entityToUser(results.next()));
		    }
		    return resultList;
	}

	@Override
	public Long createUser(User user) {
		
		IncompleteKey key = keyFactory.newKey();
		FullEntity<IncompleteKey> incUserEntity = Entity.newBuilder(key)
		.set(User.EMAIL, user.getEmail())
		.set(User.USERNAME, user.getUsername())
		.set(User.HASH, user.getHash())
		.set(User.ACTHASH, user.getActHash())
		.set(User.EMAILVERIFIED, user.getEmailVerified())
		.set(User.ISADMIN, user.isAdmin())
		.set(User.IMAGE_URL, user.getImageUrl())
		.set(User.ROLE, user.getRole())
		.build();
		
		Entity userEntity = datastore.add(incUserEntity);
		
		return userEntity.getKey().getId();
	}
	
	@Override
	public void verifyEmail(Long uid) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		User user = entityToUser(entity);
		user.setEmailVerified(true);
		editUser(user);
	}
	
	@Override
	public void setAdminPass(String pass) {
		String hash = null;
		
		try {
			hash = PasswordStorage.createHash(pass);
		} catch (CannotPerformOperationException e) {
			e.printStackTrace();
		}
		
		IncompleteKey key = hashFactory.newKey();
		FullEntity<IncompleteKey> incEnt = Entity.newBuilder(key)
			.set("hash", hash)
			.build();
		datastore.add(incEnt);
		
	}
	
	@Override
	public Boolean verifyAdmin(String pass) {
		boolean valid = false;
		EntityQuery query = Query.newEntityQueryBuilder()
				.setKind("Hash")
				.build();
		QueryResults<Entity> result = datastore.run(query);
		Entity admin = result.next();
		String hash = admin.getString("hash");
		try {
			if (PasswordStorage.verifyPassword(pass, hash)) {
				valid = true;
			}
		} catch (CannotPerformOperationException | InvalidHashException e) {
			e.printStackTrace();
		}
		return valid;
	}
	
	@Override
	public User grantAdmin(Long uid) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		User user = entityToUser(entity);
		Key key = keyFactory.newKey(user.getId());
	    Entity newEntity = Entity.newBuilder(key)
	        .set(User.EMAIL, user.getEmail())
	        .set(User.USERNAME, user.getUsername())
	        .set(User.ACTHASH, getActHash(user.getId()))
	        .set(User.HASH, getHash(user.getId()))
	        .set(User.EMAILVERIFIED, user.getEmailVerified())
	        .set(User.ISADMIN, true)
	        .set(User.IMAGE_URL, user.getImageUrl())
	        .set(User.ROLE, "Board Member")
	        .build();
		datastore.update(newEntity);
		return getUser(uid);
	}

	@Override
	public User getUser(Long uid) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		return entityToUser(entity);
	}
	
	@Override
	public void addBookmark(User user, String postId) {
		// TODO
	}
	
	@Override
	public Result<Object> listBkmks(User user) {
		// TODO 
		return null;
	}
	
	@Override
	public String getHash(Long uid) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		return entity.getString(User.HASH);
	}
	
	@Override
	public void setHash(Long uid, String hash) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		User user = entityToUser(entity);
		Key key = keyFactory.newKey(uid);
		Entity inc = Entity.newBuilder(key)
		        .set(User.EMAIL, user.getEmail())
		        .set(User.USERNAME, user.getUsername())
		    	.set(User.ACTHASH, getActHash(user.getId()))
		    	.set(User.HASH, hash)
		        .set(User.EMAILVERIFIED, user.getEmailVerified())
		        .set(User.ISADMIN, user.isAdmin())
		        .set(User.IMAGE_URL, user.getImageUrl())
		        .set(User.ROLE, user.getRole())
		        .build();
		datastore.update(inc);
	}
	
	@Override
	public String getActHash(Long uid) {
		Entity entity = datastore.get(keyFactory.newKey(uid));
		return entity.getString(User.ACTHASH);
	}

	@Override
	public void editUser(User user) {
		Key key = keyFactory.newKey(user.getId());
	    Entity entity = Entity.newBuilder(key)
	        .set(User.EMAIL, user.getEmail())
	        .set(User.USERNAME, user.getUsername())
	    	.set(User.ACTHASH, getActHash(user.getId()))
	    	.set(User.HASH, getHash(user.getId()))
	        .set(User.EMAILVERIFIED, user.getEmailVerified())
	        .set(User.ISADMIN, user.isAdmin())
	        .set(User.IMAGE_URL, user.getImageUrl())
	        .set(User.ROLE, user.getRole())
	        .build();
		datastore.update(entity);
	}

	@Override
	public void deleteUser(Long userId) {
		Key key = keyFactory.newKey(userId);
	    datastore.delete(key); 
	}

	@Override
	public Result<User> listUsers(String startCursor) {
		// TODO
		return null;
	}
	
	@Override
	public Result<User> listAdmins() {
		Query<Entity> query = Query.newEntityQueryBuilder()
	            .setKind("User") 
	            .setFilter(PropertyFilter.eq(User.ISADMIN, true))
	            .build();
		QueryResults<Entity> resultList = datastore.run(query);
	    List<User> resultAdmins = entitiesToUsers(resultList);
	    return new Result<>(resultAdmins);
	}
}
<file_sep># mchistory

This was the first web application I ever built. I was learning everything on my own as I was building it, so it's rough around the edges and contains a plethora of terrible coding/webdev practices. I just haven't had time to go back and fix it up yet.

Some of the technologies/features of the site include:
- Responsive UI for mobile browsing
- Google Datastore API for database operations
- Google Storage API for file storage/serving
- Google Domains for domain registration and DNS configuration
- Mailgun email API for user email verification, tech support requests, password recovery
- Vultr for server hosting
- Ability for users to create accounts, edit profile
- Ability for admin users to create/edit/delete content on the site
<file_sep>package com.boulder.mchistory.objects;

import com.google.cloud.Timestamp;

// [START example]
public class Post {
// [START book]
  private String type;
  private String title;
  private String createdBy;
  private String createdById;
  private Timestamp dateCreated;
  private String info;
  private Long id;
  private String imageUrl;
  private String videoUrl;
  private String album;
  private Timestamp date;
  private String dateString;
  private String time; 
  private String location;
// [END book]
// [START keys]
  public static final String TYPE = "type";
  public static final String CREATED_BY = "createdBy";
  public static final String CREATED_BY_ID = "createdById";
  public static final String DATECREATED = "dateCreated";
  public static final String INFO = "info";
  public static final String ID = "id";
  public static final String TITLE = "title";
  public static final String IMAGE_URL = "imageUrl";
  public static final String VIDEO_URL = "videoUrl";
  public static final String ALBUM = "album";
  public static final String DATE = "date";
  public static final String DATESTRING = "dateString";
  public static final String TIME = "time";
  public static final String LOCATION = "location";
// [END keys]
// [START constructor]
  // We use a Builder pattern here to simplify and standardize construction of Book objects.
  private Post(Builder builder) {
	this.type = builder.type;
    this.title = builder.title;
    this.createdBy = builder.createdBy;
    this.createdById = builder.createdById;
    this.dateCreated = builder.dateCreated;
    this.info = builder.info;
    this.id = builder.id;
    this.imageUrl = builder.imageUrl;
    this.videoUrl = builder.videoUrl;
    this.album = builder.album;
    this.date = builder.date;
    this.dateString = builder.dateString;
    this.time = builder.time;
    this.location = builder.location;
  }
// [END constructor]
// [START builder]
  public static class Builder {
	private String type;
    private String title;
    private String createdBy;
    private String createdById;
    private Timestamp dateCreated;
    private String info;
    private Long id;
    private String imageUrl;
    private String videoUrl;
    private String album;
    private Timestamp date;
    private String dateString;
    private String time;
    private String location;
    
    public Builder type(String type) {
    	this.type = type;
    	return this;
    }

    public Builder title(String title) {
      this.title = title;
      return this;
    }

    public Builder createdBy(String createdBy) {
      this.createdBy = createdBy;
      return this;
    }

    public Builder createdById(String createdById) {
      this.createdById = createdById;
      return this;
    }
    
    public Builder dateCreated(Timestamp dateCreated) {
    	this.dateCreated = dateCreated;
    	return this;
    }

    public Builder info(String info) {
      this.info = info;
      return this;
    }

    public Builder id(Long id) {
      this.id = id;
      return this;
    }

    public Builder imageUrl(String imageUrl) {
      this.imageUrl = imageUrl;
      return this;
    }
    
    public Builder videoUrl(String videoUrl) {
      this.videoUrl = videoUrl;
      return this;
    }
    
    public Builder album(String album) {
    	this.album = album;
    	return this;
    }
    
    public Builder date(Timestamp date) {
    	this.date = date;
    	return this;
    }
    
    public Builder dateString(String dateString) {
    	this.dateString = dateString;
    	return this;
    }
    
    public Builder time(String time) {
    	this.time = time;
    	return this;
    }
    
    public Builder location(String location) {
    	this.location = location;
    	return this;
    }

    public Post build() {
      return new Post(this);
    }
  }
  
  public String getType() {
	  return type;
  }
  
  public void setType(String type) {
	  this.type = type;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getCreatedBy() {
    return createdBy;
  }

  public void setCreatedBy(String createdBy) {
    this.createdBy = createdBy;
  }

  public String getCreatedById() {
    return createdById;
  }

  public void setCreatedById(String createdById) {
    this.createdById = createdById;
  }
  
  public Timestamp getDateCreated() {
	  return dateCreated;
  }
  
  public void setDateCreated(Timestamp dateCreated) {
	  this.dateCreated = dateCreated;
  }

  public String getInfo() {
    return info;
  }

  public void setInfo(String info) {
    this.info = info;
  }

  public Long getId() {
    return id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getImageUrl() {
    return imageUrl;
  }

  public void setImageUrl(String imageUrl) {
    this.imageUrl = imageUrl;
  }
  
  public String getVideoUrl() {
	  return videoUrl;
  }
  
  public void setVideoUrl(String videoUrl) {
	  this.videoUrl = videoUrl;
  }
  
  public String getAlbum() {
	  return album;
  }
  
  public void setAlbum(String album) {
	  this.album = album;
  }
  
  public Timestamp getDate() {
	  return date;
  }
  
  public void setDate(Timestamp date) {
	  this.date = date;
  }
  
  public String getDateString() {
	  return dateString;
  }
  
  public void setDateString(String dateString) {
	  this.dateString = dateString;
  }
  
  public String getTime() {
	  return time;
  }
  
  public void setTime(String time) {
	  this.time = time;
  }
  
  public String getLocation() {
	  return location;
  }
  
  public void setLocation(String location) {
	  this.location = location;
  }
  
// [END builder]
  @Override
  public String toString() {
    return
        "Title: " + title + ", Added by: " + createdBy;
  }
}
// [END example]
