I Need a Bargain
  • Home
  • Bargain Boutique
    • Prescription Drugs
    • Printable Coupons >
      • Coupons.com
      • CouponNetwork.com
    • Reward Credit Cards
  • Blog
API HOME
GET categories
GET deals
GET promotypes
GET search
GET stores
GET tags

ApiController.groovy


A Controller class written in Grails framework that authorizes API callers and fulfill API requests enabled by 3Scale. It is this class written in Groovy that serves all the API request outlined on the left. You may find the code snippet in Groovy useful when integrating with API Proxy - 3Scale.


ApiController.groovy:
package hko.dealnews.controller

import java.text.SimpleDateFormat
import java.util.List;
import net.threescale.api.v2.*
import hko.dealnews.helper.ThreeScaleApi

import hko.dealnews.domain.*
import hko.dealnews.util
import grails.converters.JSON
import groovy.json.*


/**
 * To add an API method to this Controller, please also add a mapping to UrlMappings.groovy
 * 
 * Sample code please see: https://github.com/3scale/3scale_ws_api_for_java/blob/master/src/test/java/net/threescale/api/v2/Example.java
 * 
 * @author hko
 *
 */
class ApiController {
	private static Api2 apiServer = ThreeScaleApi.getInstance()
	private String clientAppId
	private clientAppKey
	long authenticatedIn	// millisecond
	
	def dealService
	
	def beforeInterceptor = [action: this.&authorizeBy3Scale]
	def afterInterceptor = [action: this.&reportTo3Scale]
	
	////////////
	// private helper functions borrowed from https://github.com/3scale/3scale_ws_api_for_java/blob/master/src/test/java/net/threescale/api/v2/Example.java
	
	private String nowTimeStamp(Date timestamp) {
		SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
		return dateFormatter.format(timestamp);
	}

	private int maxDailyHits(AuthorizeResponse response) {
		ApiUsageMetric metric = findMetricForHitsPerDay(response);
		return (metric != null) ? Integer.parseInt(metric.getMaxValue()) : 0;
	}

	private int currentDailyHits(AuthorizeResponse response) {
		ApiUsageMetric metric = findMetricForHitsPerDay(response);
		return (metric != null) ? Integer.parseInt(metric.getCurrentValue()) : 0;
	}

	private ApiUsageMetric findMetricForHitsPerDay(AuthorizeResponse response) {
		return findMetricForPeriod(response.getUsageReports(), "hits", "day");
	}

	// Find a specific metric/period usage metric

	private ApiUsageMetric findMetricForPeriod(ArrayList usage_reports, String metric_key, String period_key) {
		for (ApiUsageMetric metric : usage_reports) {
			if (metric.getMetric().equals(metric_key) && metric.getPeriod().equals(period_key)) {
				return metric;
			}
		}
		return null;
	}
	
	// end private helper functions borrowed from 3scale example
	////////////
	
	/**
	 * A private helper function that authorizes the API caller by 3Scale
	 * 
	 * https://support.3scale.net/forum/topics/authorization-problem
	 * 
	 * @return
	 */
	private authorizeBy3Scale() {
		long t1 = new Date().getTime()
		
		clientAppId = request.getHeader("X-INAB-Application-Id")
		clientAppKey = request.getHeader("X-INAB-REST-API-Key")
		
		try{
			AuthorizeResponse authorizeResponse = apiServer.authorize(clientAppId, clientAppKey, null, null); //https://github.com/3scale/3scale_ws_api_for_java/blob/master/src/main/java/net/threescale/api/v2/Api2.java
						
			// check if api key is valid
			if (!authorizeResponse.getAuthorized()) {
				String reason = authorizeResponse.getReason().replaceAll(/\n/, /\\\n/).replaceAll(/"/, /\\"/).replaceAll('\\\\\\\\"', '\\\\\\\\\\\\"')
				log.warn("Api user (App ID:${clientAppId}, API Key:${clientAppKey}) is not authorized because " + reason);					
				String json = [
					"error": reason] as JSON
				render(contentType:"application/json", text: JsonOutput.prettyPrint(json))					
				return false	//  intercepted action to not be processed
			}
			
			// check if resource limit not reached yet
			if ((currentDailyHits(authorizeResponse) + 1) >= maxDailyHits(authorizeResponse)) {
				log.warn("Api user (App ID:${clientAppId}, API Key:${clientAppKey}) exceeds max daily hits (" + maxDailyHits(authorizeResponse) + ").")
				String json = [
					"error": "max daily hits (" + maxDailyHits(authorizeResponse) + ") exceeded"] as JSON
				render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
				return false
			}
						
			long t2 = new Date().getTime()
			authenticatedIn = (t2-t1)
			
		} catch (ApiException e) {
			if (e.getErrorCode() == "404") {
				// app_id is invalid
				throw new RuntimeException(e.getErrorMessage());
			} else if (e.getErrorCode() == "403") {
				// provider_key is invalid
				throw new RuntimeException(e.getErrorMessage());
			} else {
				// Some other error, handle as needed.
				e.printStackTrace();
			}
			return false
		}
		
	}
	
	/**
	 * A private helper that report API usage to 3Scale.  
	 * 
	 * @param model
	 * @return
	 */
	private reportTo3Scale(model){
		ApiTransaction[] transactions = new ApiTransaction[1];
		HashMap metrics0 = new HashMap();
		metrics0.put("hits", "1");	// increase app's hit count by 1
		transactions[0] = new ApiTransactionForAppId(clientAppId, nowTimeStamp(new Date()), metrics0);
		apiServer.report(transactions);
	}

	/**
	 * A private helper that execute HQL to get back INABTag's
	 * 
	 * @param params A hash map of parameters to be plugged into HQL. e.g. [type:"promo type" ,sort: "id"]
	 * @return
	 */
	private def findINABTags(params){
		INABTag[] inabTags
		if (!params.type){
			inabTags = INABTag.executeQuery("\
				SELECT tag FROM INABTag tag \
				ORDER BY tag.${params.sort}")
		}
		else if (params.type){
			inabTags = INABTag.executeQuery("\
				SELECT tag FROM INABTag tag \
				WHERE tag.type = '${params.type}' \
				ORDER BY ${params.sort}")
		}

		def results = []
		inabTags.each {
			results.add(['id': it.id, 'name': it.name, 'type': it.type, 'countAll':it.countAll, 'count30':it.count30, 'count7':it.count7])
		}
		return results
	}

	//
	// URL to Action mapping is defined in UrlMappings.groovy
	//

	/**
	 * http://blog.ineedabargain.com/get-deals.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \
	 *  http://www.ineedabargain.com/api/deals?tag_id=1
	 */
	def dealsGET = {
		long t1 = new Date().getTime()

		String httpHostPort = util.baseUrl()

		//
		int limit = Math.min(params.limit ? params.int('limit') : 20, 1000) // page size
		int skip = params.skip ? params.int('skip') : 0        // 0-based offset
		if (skip > 1000) skip = 1000
		
		List refineByTagList = null
		Long tagId = params.tag_id as Long
		List rssitems = dealService.taggedBargains(tagId, limit, skip, refineByTagList)
		long exactCount = dealService.exactMatchedCount
		String imgUrl
		
		def results = []
		rssitems.each{
			if (it.imageFlag){
				imgUrl = httpHostPort + it.returnDirName() + it.returnFileName()
			}
			else{
				imgUrl = httpHostPort + '/images/no_image.jpg'
			}
			
			results.add([
				'id': it.id, 'title': it.title,
				'url': httpHostPort + '/deals/show/'+it.id+'/'+it.limitedSEOTitle(),
				'image': imgUrl,
				'published_at': it.pubdate.format('yyyy-MM-dd HH:mm:ss Z')])
		}
		
		//
		INABTag inabtag = INABTag.get(tagId)
		
		//
		
		long t2 = new Date().getTime()
		String json = [
			"authorized_in": authenticatedIn/1000,
			"processed_in": (t2-t1)/1000,
			"tag_id": tagId,
			"tag_name": inabtag?.name,
			"tag_type": inabtag?.type,
			"tag_countAll": inabtag?.countAll,
			"tag_count30": inabtag?.count30,
			"tag_count7": inabtag?.count7,
			"limit": limit,
			"skip": skip,
			"exact_count": exactCount,
			"results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}
	
	
	/**
	 * http://blog.ineedabargain.com/get-search.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \
	 *  http://www.ineedabargain.com/api/search?q=usb+drive\&limit=5\&skip=0
	 */
	def searchGET = {
		long t1 = new Date().getTime()

		String httpHostPort = util.baseUrl()
		
		//	
		int limit = Math.min(params.limit ? params.int('limit') : 20, 1000) // page size
		int skip = params.skip ? params.int('skip') : 0        // 0-based offset
		if (skip > 1000) skip =1000
		
		List refineByTagList = null
		String dr = "all"
		List rssitems = dealService.searchBargains(params.q, limit, skip, dr, refineByTagList) 
		long estimatedCount = dealService.estimatedMatchedCount
		String imgUrl
		
		def results = []
		rssitems.each{
			if (it.imageFlag){
				imgUrl = httpHostPort + it.returnDirName() + it.returnFileName()
			}
			else{
				imgUrl = httpHostPort + '/images/no_image.jpg'
			}
			
			results.add([
				'id': it.id, 'title': it.title, 
				'url': httpHostPort + '/deals/show/'+it.id+'/'+it.limitedSEOTitle(), 
				'image': imgUrl,
				'published_at': it.pubdate.format('yyyy-MM-dd HH:mm:ss Z')])
		}
		long t2 = new Date().getTime()
		String json = [
			"authorized_in": authenticatedIn/1000,
			"processed_in": (t2-t1)/1000,
			"query": params.q,
			"limit": limit,
			"skip": skip, 
			"estimated_count": estimatedCount,
			"results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}
	
	
	
	/**
	 * http://blog.ineedabargain.com/get-tags.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \
	 *  http://www.ineedabargain.com/api/tags
	 */
	def tagsGET = {
		long t1 = new Date().getTime()		
		def results = findINABTags([sort: "id"])
		long t2 = new Date().getTime()		
		String json = ["authorized_in": authenticatedIn/1000, "processed_in": (t2-t1)/1000, "results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}

	
	
	/**
	 * http://blog.ineedabargain.com/get-categories.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \
	 *  http://localhost:8080/dealnews/api/categories
	 */
	def categoriesGET = {
		long t1 = new Date().getTime()
		def results = findINABTags([type:"category", sort: "id"])
		long t2 = new Date().getTime()
		String json = ["authorized_in": authenticatedIn/1000, "processed_in": (t2-t1)/1000, "results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}

	
	
	/**
	 * http://blog.ineedabargain.com/get-stores.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \
	 *  http://www.ineedabargain.com/api/stores
	 */
	def storesGET = {
		long t1 = new Date().getTime()
		def results = findINABTags([type:"store", sort: "id"])
		long t2 = new Date().getTime()		
		String json = ["authorized_in": authenticatedIn/1000, "processed_in": (t2-t1)/1000, "results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}

	
	
	/**
	 * http://blog.ineedabargain.com/get-promotypes.html
	 * 
	 * e.g. curl -X GET \
	 * 	-H "X-INAB-Application-Id: cd2b6b71" \
	 *  -H "X-INAB-REST-API-Key: 24228b4725720900ab8d00449c4cc6fc" \ 
	 *  http://www.ineedabargain.com/api/promotypes
	 */
	def promotypesGET = {
		long t1 = new Date().getTime()
		def results = findINABTags([type:"promo type", sort: "id"])
		long t2 = new Date().getTime()		
		String json = ["authorized_in": authenticatedIn/1000, "processed_in": (t2-t1)/1000, "results": results] as JSON
		render(contentType:"application/json", text: JsonOutput.prettyPrint(json))
	}
}


Need more help?

For API feature request, bug report, or any feedback related to API, please email team [at] ineedabargain.com.
© 2010-2019 I Need a Bargain
Home | About | Privacy Policy