1 /**
2 	Copyright: © 2013-2016 rejectedsoftware e.K.
3 	License: Subject to the terms of the GNU GPLv3 license, as written in the included LICENSE.txt file.
4 	Authors: Colden Cullen
5 */
6 module dubregistry.api;
7 
8 import dubregistry.dbcontroller;
9 import dubregistry.registry;
10 
11 import std.algorithm.iteration : map;
12 import std.array : array;
13 import std.exception : enforce;
14 import vibe.data.json : Json;
15 import vibe.http.router;
16 import vibe.inet.url;
17 import vibe.textfilter.urlencode;
18 import vibe.web.rest;
19 
20 
21 /** Registers the DUB registry REST API endpoints in the given router.
22 */
23 void registerDubRegistryAPI(URLRouter router, DubRegistry registry)
24 {
25 	auto pkgs = new LocalDubRegistryAPI(registry);
26 	router.registerRestInterface(pkgs, "/api");
27 	router.get("/api/packages/dump", (req, res) @trusted => dumpPackages(req, res, registry));
28 }
29 
30 /// Compatibility alias.
31 deprecated("Use registerDubRegistryAPI instead.")
32 alias registerDubRegistryWebApi = registerDubRegistryAPI;
33 
34 
35 private void dumpPackages(HTTPServerRequest req, HTTPServerResponse res, DubRegistry registry)
36 {
37 	import vibe.data.json : serializeToPrettyJson;
38 	import vibe.stream.wrapper : StreamOutputRange;
39 
40 	res.contentType = "application/json; charset=UTF-8";
41 	res.headers["Content-Encoding"] = "gzip"; // force GZIP compressed response
42 	auto dst = StreamOutputRange(res.bodyWriter);
43 	dst.put('[');
44 	bool first = true;
45 	foreach (p; registry.getPackageDump()) {
46 		if (!first) dst.put(',');
47 		else first = false;
48 		serializeToPrettyJson(&dst, p);
49 	}
50 	dst.put(']');
51 }
52 
53 /** Returns a REST client instance for communicating with a DUB registry's API.
54 
55 	Params:
56 		url = URL of the DUB registry (e.g. "https://code.dlang.org/")
57 */
58 DubRegistryAPI connectDubRegistryAPI(URL url)
59 {
60 	return new RestInterfaceClient!DubRegistryAPI(url);
61 }
62 /// ditto
63 DubRegistryAPI connectDubRegistry(string url)
64 {
65 	return connectDubRegistryAPI(URL(url));
66 }
67 
68 interface DubRegistryAPI {
69 	@property IPackages packages();
70 }
71 
72 struct SearchResult { string name, description, version_; }
73 struct DownloadStats { DbDownloadStats downloads; }
74 
75 interface IPackages {
76 	@method(HTTPMethod.GET)
77 	SearchResult[] search(string q = "");
78 
79 	@path(":name/latest")
80 	string getLatestVersion(string _name);
81 
82 	@path(":name/stats")
83 	DbPackageStats getStats(string _name);
84 
85 	@path(":name/:version/stats")
86 	DownloadStats getStats(string _name, string _version);
87 
88 	@path(":name/info")
89 	Json getInfo(string _name);
90 
91 	@path(":name/:version/info")
92 	Json getInfo(string _name, string _version);
93 }
94 
95 class LocalDubRegistryAPI : DubRegistryAPI {
96 	private {
97 		Packages m_packages;
98 	}
99 
100 	this(DubRegistry registry)
101 	{
102 		m_packages = new Packages(registry);
103 	}
104 
105 	@property Packages packages() { return m_packages; }
106 }
107 
108 class Packages : IPackages {
109 	private {
110 		DubRegistry m_registry;
111 	}
112 
113 	this(DubRegistry registry)
114 	{
115 		m_registry = registry;
116 	}
117 
118 override {
119 	@method(HTTPMethod.GET)
120 	SearchResult[] search(string q) {
121 		return m_registry.searchPackages(q)
122 			.map!(p => SearchResult(p.name, p.info["description"].opt!string, p.version_))
123 			.array;
124 	}
125 
126 	string getLatestVersion(string name) {
127 		return m_registry.getLatestVersion(rootOf(name))
128 			.check!(r => r.length)(HTTPStatus.notFound, "Package not found");
129 	}
130 
131 	DbPackageStats getStats(string name) {
132 		try {
133 			auto stats = m_registry.getPackageStats(rootOf(name));
134 			return stats;
135 		} catch (RecordNotFound e) {
136 			throw new HTTPStatusException(HTTPStatus.notFound, "Package not found");
137 		}
138 	}
139 
140 	DownloadStats getStats(string name, string ver) {
141 		try {
142 			return typeof(return)(m_registry.getDownloadStats(rootOf(name), ver));
143 		} catch (RecordNotFound e) {
144 			throw new HTTPStatusException(HTTPStatus.notFound, "Package or Version not found");
145 		}
146 	}
147 
148 	Json getInfo(string name) {
149 		return m_registry.getPackageInfo(rootOf(name))
150 			.check!(r => r.info.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found")
151 			.info;
152 	}
153 
154 	Json getInfo(string name, string ver) {
155 		return m_registry.getPackageVersionInfo(rootOf(name), ver)
156 			.check!(r => r.type != Json.Type.null_)(HTTPStatus.notFound, "Package/Version not found");
157 	}
158 }
159 
160 private:
161 	string rootOf(string pkg) {
162 		import std.algorithm: findSplitBefore;
163 		return pkg.urlDecode().findSplitBefore(":")[0];
164 	}
165 }
166 
167 
168 private	auto ref T check(alias cond, T)(auto ref T t, HTTPStatus status, string msg)
169 {
170 	enforce(cond(t), new HTTPStatusException(status, msg));
171 	return t;
172 }