From 75beec91a8526fbbc0a90134140b9dff6af15c0c Mon Sep 17 00:00:00 2001 From: Michaël Ball Date: Sun, 28 Dec 2014 12:24:22 +0000 Subject: Initial frontend work --- .gitignore | 7 +- Gruntfile.js | 28 ++++ app.db | Bin 0 -> 3072 bytes bower.json | 39 +++++ common/security.py | 15 ++ common/utils.py | 14 +- db/db_manager.py | 2 +- mach2.py | 297 ++++++++++++++++++++++++++++++++---- models/album.py | 13 +- models/track.py | 27 +++- models/user.py | 52 +++++++ package.json | 16 ++ static/partials/albums/list.html | 28 ++++ static/partials/artists/detail.html | 12 ++ static/partials/artists/list.html | 28 ++++ static/partials/artists/tracks.html | 7 + static/scripts/app/app.js | 63 ++++++++ static/scripts/app/controllers.js | 148 ++++++++++++++++++ static/scripts/app/filters.js | 48 ++++++ static/scripts/app/services.js | 97 ++++++++++++ templates/index.html | 64 ++++++++ templates/login.html | 39 +++++ 22 files changed, 1005 insertions(+), 39 deletions(-) create mode 100644 Gruntfile.js create mode 100644 app.db create mode 100644 bower.json create mode 100644 common/security.py create mode 100644 models/user.py create mode 100644 package.json create mode 100644 static/partials/albums/list.html create mode 100644 static/partials/artists/detail.html create mode 100644 static/partials/artists/list.html create mode 100644 static/partials/artists/tracks.html create mode 100644 static/scripts/app/app.js create mode 100644 static/scripts/app/controllers.js create mode 100644 static/scripts/app/filters.js create mode 100644 static/scripts/app/services.js create mode 100644 templates/index.html create mode 100644 templates/login.html diff --git a/.gitignore b/.gitignore index a76c615..def8954 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,9 @@ library.db-journal public tmp library.db -cscope.* \ No newline at end of file +bower_components +node_modules +library.db +cscope.* +static/scripts/libs/ +.jshintrc \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..a220434 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,28 @@ +module.exports = function(grunt) { + var path = require("path"); + + grunt.initConfig({ + pkg: grunt.file.readJSON("package.json"), + bower: { + install: { + options: { + targetDir: "static/scripts/libs", + install: true, + cleanup: true, + layout: "byComponent" + } + } + }, + run: { + mach2: { + cmd: "python", + args: ["mach2.py"] + } + } + }); + + grunt.loadNpmTasks("grunt-bower-task"); + grunt.loadNpmTasks("grunt-run"); + + grunt.task.registerTask("default", ["bower:install", "run:mach2"]); +}; diff --git a/app.db b/app.db new file mode 100644 index 0000000..8078901 Binary files /dev/null and b/app.db differ diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..024e6f3 --- /dev/null +++ b/bower.json @@ -0,0 +1,39 @@ +{ + "name": "mach2", + "version": "0.0.1", + "homepage": "https://github.com/michael-ball/mach2", + "authors": [ + "Michaël Ball" + ], + "description": "Lightweight media server", + "main": "mach2.py", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "dependencies": { + "angular": "1.4.8", + "bootstrap": "3.3.5", + "moment": "~2.10.6", + "angular-moment": "~0.10.3", + "angular-ui-router": "~0.2.15", + "angular-bootstrap": "~0.14.3", + "angular-resource": "~1.4.8", + "moment-timezone": "~0.4.1" + }, + "resolutions": { + "angular": "1.3.10" + }, + "exportsOverride": { + "bootstrap": { + "js": "**/bootstrap*.js", + "css": "**/*.css*", + "fonts": "**/fonts" + } + } +} diff --git a/common/security.py b/common/security.py new file mode 100644 index 0000000..af1e8b9 --- /dev/null +++ b/common/security.py @@ -0,0 +1,15 @@ +from passlib.context import CryptContext + + +pwd_context = CryptContext( + schemes=["pbkdf2_sha256", "des_crypt"], + default="pbkdf2_sha256", + + # vary rounds parameter randomly when creating new hashes... + all__vary_rounds=0.1, + + # set the number of rounds that should be used... + # (appropriate values may vary for different schemes, + # and the amount of time you wish it to take) + pbkdf2_sha256__default_rounds=8000, + ) diff --git a/common/utils.py b/common/utils.py index 288673e..38ad5ed 100644 --- a/common/utils.py +++ b/common/utils.py @@ -12,8 +12,18 @@ def make_where_clause(params, join_operator="AND"): try: for (column, operator) in params.items(): - condition_subphrase = " ".join(("%s", operator, ":%s")) - where_items.append(condition_subphrase % (column, column)) + condition_subphrase = "" + + if operator == "BETWEEN": + condition_subphrase = " ".join(("%s", operator, + ":%s1 AND :%s2")) + + where_items.append(condition_subphrase % (column, column, + column)) + else: + condition_subphrase = " ".join(("%s", operator, ":%s")) + + where_items.append(condition_subphrase % (column, column)) where_statement = None if len(where_items) > 1: diff --git a/db/db_manager.py b/db/db_manager.py index 82168d0..c55ac39 100644 --- a/db/db_manager.py +++ b/db/db_manager.py @@ -172,5 +172,5 @@ class DbManager: def __new__(self): if not DbManager.instance: DbManager.instance = DbManager.__DbManager() - + return DbManager.instance diff --git a/mach2.py b/mach2.py index ffa1c1e..7d4ff68 100644 --- a/mach2.py +++ b/mach2.py @@ -13,10 +13,10 @@ from flask.ext.compress import Compress from flask.ext.login import LoginManager, current_user, login_required from flask.ext.login import login_user, logout_user - from models.album import Album from models.artist import Artist from models.track import Track +from models.user import User DATABASE = "app.db" @@ -24,6 +24,8 @@ DATABASE = "app.db" compress = Compress() app = Flask(__name__) +app.secret_key = """\xfc[\x16\x9d\x0f\x86;;\x9e_\x96\x01\xb7\xeay^\x8b\xa0E\x84 + \x91;\x18\xc2""" app.config.from_object(__name__) config = configparser.ConfigParser() @@ -36,6 +38,38 @@ login_manager.login_view = "login" login_manager.session_protection = "strong" +def get_db(): + db = getattr(g, "_database", None) + if db is None: + db = sqlite3.connect(DATABASE) + db.row_factory = sqlite3.Row + setattr(g, "_database", db) + + return db + + +@app.teardown_appcontext +def close_connection(exception): + db = getattr(g, "_database", None) + if db is not None: + db.close() + + +def query_db(query, args=(), one=False): + cur = get_db().execute(query, args) + rv = cur.fetchall() + cur.close() + return (rv[0] if rv else None) if one else rv + + +config = configparser.ConfigParser() +config.read("mach2.ini") + +login_manager = LoginManager() +login_manager.login_view = "login" +login_manager.session_protection = "strong" + + def get_db(): db = getattr(g, "_database", None) if db is None: @@ -61,41 +95,140 @@ def query_db(query, args=(), one=False): @app.route("/") -def hello(): - return "Hello world!" +@login_required +def index(): + return render_template("index.html", user=current_user) -@app.route("/search/album/") -def album_search(album_name): +@app.route("/albums") +@login_required +def albums(): + returned_albums = [] albums = [] - for album in Album.search(name={'data': album_name, 'operator': 'LIKE'}): + + order_by = request.args.get("order", None) + order_direction = request.args.get("direction", None) + lim = request.args.get("limit", None) + off = request.args.get("offset", None) + conditions = request.args.getlist("conditions") + + search_params = {} + + if conditions: + field = conditions[0] + operator = conditions[1] + value = conditions[2] + + search_params[field] = {"data": value, "operator": operator} + + params = {} + + if order_by: + params["order"] = order_by + + if order_direction: + params["direction"] = order_direction + + if lim: + params["limit"] = lim + + if off: + params["offset"] = off + + all_params = params.copy() + all_params.update(search_params) + + if search_params: + returned_albums = Album.search(**all_params) + else: + returned_albums = Album.all(**params) + + for album in returned_albums: albums.append(album.__dict__) return json.dumps(albums) -@app.route("/search/artist/") -def artist_search(artist_name): +@app.route("/albums//tracks") +@login_required +def album_tracks(album_id): + tracks = [] + album = Album(id=album_id) + + for track in album.tracks: + tracks.append(track.__dict__) + + return json.dumps(tracks) + + +@app.route("/albums//artists") +@login_required +def album_artists(album_id): artists = [] - for artist in Artist.search(name={ - 'data': artist_name, - 'operator': 'LIKE' - }): + album = Album(id=album_id) + + for artist in album.artists: artists.append(artist.__dict__) return json.dumps(artists) -@app.route("/search/track/") -def track_search(track_name): - tracks = [] - for track in Track.search(name={'data': track_name, 'operator': 'LIKE'}): - tracks.append(track.__dict__) +@app.route("/albums/") +@login_required +def album(album_id): + album = Album(id=album_id) - return json.dumps(tracks) + return json.dumps(album.__dict__) + + +@app.route("/albums/") +@login_required +def album_search(album_name): + albums = [] + + for album in Album.search(name={"data": album_name, "operator": "LIKE"}): + albums.append(album.__dict__) + + return json.dumps(albums) -@app.route("/artist//tracks") +@app.route("/artists") +@login_required +def artists(): + order_by = None + order_direction = None + lim = None + off = None + returned_artists = [] + artists = [] + + if request.args.get("order"): + order_by = request.args.get("order") + + if request.args.get("direction"): + order_direction = request.args.get("direction") + + if request.args.get("limit"): + lim = request.args.get("limit") + + if request.args.get("offset"): + off = request.args.get("offset") + + if order_by: + returned_artists = Artist.all(order=order_by, + direction=order_direction, + limit=lim, offset=off) + else: + returned_artists = Artist.all(limit=lim, offset=off) + + for artist in returned_artists: + artists.append(artist.__dict__) + + return json.dumps(artists) + + +@app.route("/artists//tracks") +@login_required def artist_tracks(artist_id): tracks = [] artist = Artist(id=artist_id) @@ -106,7 +239,8 @@ def artist_tracks(artist_id): return json.dumps(tracks) -@app.route("/artist//albums") +@app.route("/artists//albums") +@login_required def artist_albums(artist_id): albums = [] artist = Artist(id=artist_id) @@ -117,29 +251,75 @@ def artist_albums(artist_id): return json.dumps(albums) -@app.route("/album//tracks") -def album_tracks(album_id): +@app.route("/artists/") +@login_required +def artist_info(artist_id): + artist = Artist(id=artist_id) + + return json.dumps(artist.__dict__) + + +@app.route("/artists/") +@login_required +def artist_search(artist_name): + artists = [] + for artist in Artist.search(name={ + "data": artist_name, + "operator": "LIKE" + }): + artists.append(artist.__dict__) + + return json.dumps(artists) + + +@app.route("/tracks") +@login_required +def tracks(): + order_by = None + order_direction = None + lim = None + off = None + returned_tracks = [] tracks = [] - album = Album(id=album_id) - for track in album.tracks: + if request.args.get("order"): + order_by = request.args.get("order") + + if request.args.get("direction"): + order_direction = request.args.get("direction") + + if request.args.get("limit"): + lim = request.args.get("limit") + + if request.args.get("offset"): + off = request.args.get("offset") + + if order_by: + returned_tracks = Track.all(order=order_by, direction=order_direction, + limit=lim, offset=off) + else: + returned_tracks = Track.all(limit=lim, offset=off) + + for track in returned_tracks: tracks.append(track.__dict__) return json.dumps(tracks) -@app.route("/album//artists") -def album_artists(album_id): +@app.route("/tracks//artists") +@login_required +def track_artists(track_id): artists = [] - album = Album(id=album_id) + track = Track(id=track_id) - for artist in album.artists: + for artist in track.artists: artists.append(artist.__dict__) return json.dumps(artists) -@app.route("/track/") +@app.route("/tracks/") +@login_required def track(track_id): def stream_file(filename, chunksize=8192): with open(filename, "rb") as f: @@ -171,10 +351,63 @@ def track(track_id): return resp -if __name__ == "__main__": - config = configparser.ConfigParser() - config.read("mach2.ini") +@app.route("/tracks/") +@login_required +def track_search(track_name): + tracks = [] + for track in Track.search(name={"data": track_name, "operator": "LIKE"}): + tracks.append(track.__dict__) + + return json.dumps(tracks) + + +@login_manager.user_loader +def load_user(userid): + user = None + result = query_db("SELECT * FROM user WHERE id = ?", [userid], one=True) + + if result: + user = User(id=result[0], username=result[1], password_hash=result[2], + authenticated=1, active=result[4], anonymous=0) + + return user + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + if request.method == "POST": + user = None + result = query_db("SELECT * FROM user WHERE username = ?", + [request.form["username"]], one=True) + + if result: + user = User(id=result[0], + username=result[1], + password_hash=result[2], + authenticated=0, + active=result[4], + anonymous=result[5]) + + password = request.form["password"] + + if user and user.verify(password): + login_user(user) + return redirect(request.args.get("next") or url_for("index")) + else: + user = None + + return render_template("login.html") + + +@app.route("/logout") +@login_required +def logout(): + logout_user() + return redirect("/") + + +if __name__ == "__main__": login_manager.init_app(app) compress.init_app(app) diff --git a/models/album.py b/models/album.py index 9ca3798..0d7cd54 100644 --- a/models/album.py +++ b/models/album.py @@ -99,7 +99,8 @@ class Album(): sql = " ".join(("UPDATE album"), set_clause, "WHERE id = :id") cursor.execute(sql, dirty_attributes) - def search(**search_params): + def search(order="album.id", direction="ASC", limit=None, + offset=None, **search_params): """Find an album with the given params Args: @@ -117,7 +118,14 @@ class Album(): value_params = {} for (attr, value) in search_params.items(): where_params[attr] = value["operator"] - value_params[attr] = value["data"] + + if value["operator"] == "BETWEEN": + items = value["data"].split(" ") + + value_params["".join((attr, "1"))] = items[0] + value_params["".join((attr, "2"))] = items[2] + else: + value_params[attr] = value["data"] where_clause = utils.make_where_clause(where_params) @@ -138,6 +146,7 @@ class Album(): def all(order="album.id", direction="ASC", limit=None, offset=None): db = DbManager() cursor = db.cursor() + albums = [] select_string = """SELECT * FROM album LEFT JOIN album_artist ON diff --git a/models/track.py b/models/track.py index dead1f8..688f6ff 100644 --- a/models/track.py +++ b/models/track.py @@ -361,7 +361,7 @@ class Track: for artist_name in artist_names: musicbrainz_artistid = None - artistsort = None + artistsort = artist_name try: musicbrainz_artistid = musicbrainz_artist_ids[i] except IndexError: @@ -543,3 +543,28 @@ class Track: db.commit() return True + + def all(order="track.id", direction="ASC", limit=None, offset=None): + db = DbManager() + tracks = [] + + select_string = """SELECT * FROM track LEFT JOIN artist_track ON + artist_track.track_id = track.id LEFT JOIN artist ON + artist_track.artist_id = artist.id LEFT JOIN album_track ON + album_track.track_id = track.id LEFT JOIN album ON + album_track.album_id = album.id ORDER BY %s %s""" % (order, + direction) + + if limit is not None and offset is not None: + select_string = " ".join((select_string, + "LIMIT %s OFFSET %s" % (limit, offset))) + + result = db.execute(select_string) + + for row in result: + tracks.append( + Track(id=row[0], tracknumber=row[1], name=row[3], + grouping=row[3], filename=row[4]) + ) + + return tracks diff --git a/models/user.py b/models/user.py new file mode 100644 index 0000000..f912e43 --- /dev/null +++ b/models/user.py @@ -0,0 +1,52 @@ +from common.security import pwd_context + + +class User: + def __init__(self, **kwargs): + for (key, value) in kwargs.items(): + setattr(self, key, value) + + def get_id(self): + if self.id: + return str(self.id) + else: + raise ValueError("No user") + + def is_authenticated(self): + if self.authenticated > 0: + return True + else: + return False + + def is_active(self): + if self.active > 0: + return True + else: + return False + + def is_anonymous(self): + if self.anonymous > 0: + return True + else: + return False + + def verify(self, password): + if self.id and pwd_context.verify(password, self.password_hash): + self.authenticated = 1 + return True + else: + return False + + def new_password(self, password, category=None): + if self.id: + hash = None + + if category: + hash = pwd_context.encrypt(password, category=category) + else: + hash = pwd_context.encrypt(password) + + return hash + + else: + raise ValueError("No user") diff --git a/package.json b/package.json new file mode 100644 index 0000000..aa0ff4d --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "mach2", + "version": "0.0.1", + "description": "Lightweight media server", + "homepage": "https://github.com/michael-ball/mach2", + "license": "MIT", + "author": { + "name": "Michaël Ball", + "url": "https://www.github.com/michael-ball" + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-bower-task": "^0.4.0", + "grunt-run": "^0.3.0" + } +} diff --git a/static/partials/albums/list.html b/static/partials/albums/list.html new file mode 100644 index 0000000..01ac2a8 --- /dev/null +++ b/static/partials/albums/list.html @@ -0,0 +1,28 @@ +
+
+

Albums

+
+
+
+
+ +
+
+ + diff --git a/static/partials/artists/detail.html b/static/partials/artists/detail.html new file mode 100644 index 0000000..991633a --- /dev/null +++ b/static/partials/artists/detail.html @@ -0,0 +1,12 @@ +
+

{{ artist.name }}

+ +

Albums

+
    +
  • {{album.name}} ({{ album.date | amDateFormat: 'YYYY':'':'':'YYYY-MM-DD' }})
  • +
+ +

View artist tracks

+
+
+
\ No newline at end of file diff --git a/static/partials/artists/list.html b/static/partials/artists/list.html new file mode 100644 index 0000000..7c48e57 --- /dev/null +++ b/static/partials/artists/list.html @@ -0,0 +1,28 @@ +
+
+

Artists

+
+
+
+
+ +
+
+
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/static/partials/artists/tracks.html b/static/partials/artists/tracks.html new file mode 100644 index 0000000..8d05b1d --- /dev/null +++ b/static/partials/artists/tracks.html @@ -0,0 +1,7 @@ +
+

{{ artist.name }}'s Tracks

+ + +
\ No newline at end of file diff --git a/static/scripts/app/app.js b/static/scripts/app/app.js new file mode 100644 index 0000000..b0d3738 --- /dev/null +++ b/static/scripts/app/app.js @@ -0,0 +1,63 @@ +var mach2App = angular.module( + 'mach2App', + [ + 'ui.router', + 'mach2Services', + 'mach2Controllers', + 'mach2Filters', + 'ui.bootstrap', + 'angularMoment' + ] +); + +mach2App.config( + [ + '$stateProvider', + '$urlRouterProvider', + function($stateProvider, $urlRouterProvider) { + $stateProvider.state('artists', { + url: '/artists', + templateUrl: 'static/partials/artists/list.html', + controller: 'ArtistCtrl', + }); + + $stateProvider.state('artistdetail', { + url: '/artists/{artistId:int}', + templateUrl: 'static/partials/artists/detail.html', + controller: 'ArtistDetailCtrl', + resolve: { + artistId: ['$stateParams', function($stateParams) { + return $stateParams.artistId; + }] + }, + }); + + $stateProvider.state('artistdetail.tracks', { + url: '/tracks', + templateUrl: 'static/partials/artists/tracks.html', + controller: 'ArtistTracksCtrl' + }); + + $stateProvider.state('albums', { + url: '/albums', + templateUrl: 'static/partials/albums/list.html', + controller: 'AlbumCtrl' + }); + + $stateProvider.state('albums.detail', { + url: '/{albumId:int}', + templateUrl: 'static/partials/album/detail.html', + controller: 'AlbumDetailCtrl' + }); + + $urlRouterProvider.otherwise('/artists'); + } + ] +); + +mach2App.constant( + 'angularMomentConfig', + { + timezone: 'utc' + } +); \ No newline at end of file diff --git a/static/scripts/app/controllers.js b/static/scripts/app/controllers.js new file mode 100644 index 0000000..35340e6 --- /dev/null +++ b/static/scripts/app/controllers.js @@ -0,0 +1,148 @@ +var mach2Controllers = angular.module( + 'mach2Controllers', + [ + 'ui.bootstrap', + 'angularMoment' + ] +); + +mach2Controllers.controller('NavCtrl', ['$scope', function($scope) { +}]); + +mach2Controllers.controller( + 'ArtistCtrl', + [ + '$scope', + 'ArtistSearch', + function($scope, ArtistSearch) { + $scope.totalArtists = ArtistSearch.query(); + $scope.indices = [ + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '0-9', + 'Other' + ]; + $scope.selectedIndex = $scope.indices[0]; + } + ] +); + +mach2Controllers.controller( + 'ArtistDetailCtrl', + [ + '$scope', + '$stateParams', + 'Artist', + 'ArtistAlbums', + 'ArtistTracks', + function( + $scope, + $stateParams, + Artist, + ArtistAlbums, + ArtistTracks + ) { + console.log('Am I here?'); + $scope.artist = Artist.query({ + artistId: $stateParams.artistId + }); + + $scope.albums = ArtistAlbums.query({ + artistId: $stateParams.artistId + }); + + $scope.tracks = ArtistTracks.query({ + artistId: $stateParams.artistId + }); + } + ] +); + +mach2Controllers.controller( + 'ArtistTracksCtrl', + [ + '$scope', + '$stateParams', + 'Artist', + 'ArtistTracks', + function($scope, $stateParams, Artist, ArtistTracks) { + $scope.artist = Artist.query({ + artistId: $stateParams.artistId + }); + + $scope.tracks = ArtistTracks.query({ + artistId: $stateParams.artistId + }); + } + ] +); + +mach2Controllers.controller( + 'AlbumCtrl', + [ + '$scope', + 'AlbumArtists', + 'AlbumSearch', + function($scope, AlbumArtists, AlbumSearch) { + // horrible way of calculating decades + var currentYear = moment().format('YYYY'); + var startDecade = 1940; + + var decades = []; + + for (i = (startDecade/10); i <= (currentYear/10); i++) { + decades.push(i * 10); + } + + $scope.albums = AlbumSearch.query(); + $scope.indices = decades; + $scope.selectedIndex = $scope.indices[0]; + $scope.albumArtists = AlbumArtists; + } + ] +); + +mach2Controllers.controller( + 'AlbumDetailCtrl', + [ + '$scope', + '$stateParams', + 'Album', + 'AlbumTracks', + function( + $scope, + $stateParams, + Album, + AlbumTracks + ) { + + } + ] +); + +mach2Controllers.controller('TrackCtrl', ['$scope', function($scope) { +}]); diff --git a/static/scripts/app/filters.js b/static/scripts/app/filters.js new file mode 100644 index 0000000..ed1f537 --- /dev/null +++ b/static/scripts/app/filters.js @@ -0,0 +1,48 @@ +var mach2Filters = angular.module('mach2Filters',[]); + +mach2Filters.filter('alphabetFilter', function() { + return function(items, search) { + if (!search) { + return items; + } + + return items.filter(function(element, index, array) { + var searchTerm = search.param; + var searchAttrs = search.attrs; + var regexp = new RegExp(searchTerm, 'i'); + + var searchString = null; + + for(i = 0; i < searchAttrs.length; i++) { + if (element[searchAttrs[i]]) { + searchString = element[searchAttrs[i]]; + break; + } + } + + if (searchTerm === '0-9') { + regexp = /[0-9]/; + } else if (searchTerm === 'Other') { + regexp = /\W/; + } + + if (searchString.charAt(0).match(regexp) !== null) { + return true; + } else { + return false; + } + }); + }; +}); + +mach2Filters.filter('dateFilter', function() { + return function(items, search) { + return items.filter(function(element, index, array) { + var albumDate = moment(element.date, 'YYYY-MM-DD'); + var compDate = moment(search, 'YYYY'); + var compNextDate = moment((parseInt(search) + 10), 'YYYY'); + + return (albumDate.isAfter(compDate) && albumDate.isBefore(compNextDate)); + }); + }; +}); \ No newline at end of file diff --git a/static/scripts/app/services.js b/static/scripts/app/services.js new file mode 100644 index 0000000..c7cd92b --- /dev/null +++ b/static/scripts/app/services.js @@ -0,0 +1,97 @@ +var mach2Services = angular.module('mach2Services', ['ngResource']); + +mach2Services.factory('Artist', ['$resource', function($resource) { + return $resource('artists/:artistId', {}, { + query: { + method: 'GET' + } + }); +}]); + +mach2Services.factory('ArtistAlbums', ['$resource', function($resource) { + return $resource('artists/:artistId/albums', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); + +mach2Services.factory('ArtistSearch', ['$resource', function($resource) { + return $resource('artists/:name', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); + +mach2Services.factory('ArtistTracks', ['$resource', function($resource) { + return $resource('artists/:artistId/tracks', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); + +mach2Services.factory('Album', ['$resource', function($resource) { + return $resource('albums/:albumId', {}, { + query: { + method: 'GET' + } + }); +}]); + +mach2Services.factory('AlbumArtists', ['$resource', function($resource) { + return $resource('albums/:albumId/artists', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); + +mach2Services.factory('AlbumSearch', ['$resource', function($resource) { + return $resource('albums/:name', {}, { + query: { + method: 'GET', + isArray: true, + } + }); +}]); + +mach2Services.factory('AlbumTracks', ['$resource', function($resource) { + return $resource('albums/:albumId/tracks', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); + +mach2Services.factory('Track', ['$resource', function($resource) { + return $resource('tracks/:trackId', {}, { + query: { + method: 'GET' + } + }); +}]); + +mach2Services.factory('TrackArtists', ['$resource', function($resource) { + return $resource('tracks/:trackId/artists', {}, { + query: { + method: 'GET' + } + }); +}]); + + +mach2Services.factory('TrackSearch', ['$resource', function($resource) { + return $resource('tracks/:name', {}, { + query: { + method: 'GET', + isArray: true + } + }); +}]); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..9f3d8b6 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,64 @@ + + + + + + Mach2 + + + +
+ +
+
+
+ + + + + + + + + + + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..0f3d529 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,39 @@ + + + + + + Mach2 + + + + + +
+
+

Mach2

+
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ + -- cgit v1.2.3