Skip to content

Commit a8da924

Browse files
author
midarrlabs
authored
Merge pull request #31 from midarrlabs/feature/add-continue-watching
Feature/add continue watching
2 parents a08dc05 + e9d9deb commit a8da924

File tree

34 files changed

+1052
-86
lines changed

34 files changed

+1052
-86
lines changed

assets/js/app.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,56 @@ channel.join()
7979
})
8080

8181
let liveSocket = new LiveSocket("/live", Socket, {
82-
params: { _csrf_token: csrfToken }
82+
params: { _csrf_token: csrfToken },
83+
hooks: {
84+
video: {
85+
mounted() {
86+
const urlParams = new URLSearchParams(window.location.search)
87+
88+
if (urlParams.has('seconds')) {
89+
this.el.currentTime = urlParams.get('seconds')
90+
}
91+
92+
window.addEventListener("beforeunload", event => {
93+
this.pushEvent("video_destroyed", {
94+
movie_id: window.movie_id,
95+
current_time: Math.floor(this.el.currentTime),
96+
duration: Math.floor(this.el.duration),
97+
user_id: window.userId
98+
})
99+
100+
delete event['returnValue']
101+
})
102+
},
103+
destroyed() {
104+
window.removeEventListener("beforeunload")
105+
}
106+
},
107+
episode: {
108+
mounted() {
109+
const urlParams = new URLSearchParams(window.location.search)
110+
111+
if (urlParams.has('seconds')) {
112+
this.el.currentTime = urlParams.get('seconds')
113+
}
114+
115+
window.addEventListener("beforeunload", event => {
116+
this.pushEvent("episode_destroyed", {
117+
episode_id: window.episode_id,
118+
serie_id: window.serie_id,
119+
current_time: Math.floor(this.el.currentTime),
120+
duration: Math.floor(this.el.duration),
121+
user_id: window.userId
122+
})
123+
124+
delete event['returnValue']
125+
})
126+
},
127+
destroyed() {
128+
window.removeEventListener("beforeunload")
129+
}
130+
}
131+
}
83132
})
84133

85134
liveSocket.connect()

lib/media_server/accounts.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,10 @@ defmodule MediaServer.Accounts do
234234
"""
235235
def get_user_by_session_token(token) do
236236
{:ok, query} = UserToken.verify_session_token_query(token)
237+
237238
Repo.one(query)
239+
|> Repo.preload(:movie_watch_statuses)
240+
|> Repo.preload(:episode_watch_statuses)
238241
end
239242

240243
@doc """

lib/media_server/accounts/user.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ defmodule MediaServer.Accounts.User do
99
field :hashed_password, :string, redact: true
1010
field :confirmed_at, :naive_datetime
1111
field :is_admin, :boolean
12+
has_many :movie_watch_statuses, MediaServer.WatchStatuses.Movie
13+
has_many :episode_watch_statuses, MediaServer.WatchStatuses.Episode
1214

1315
timestamps()
1416
end

lib/media_server/watch_statuses.ex

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
defmodule MediaServer.WatchStatuses do
2+
@moduledoc """
3+
The WatchStatuses context.
4+
"""
5+
6+
import Ecto.Query, warn: false
7+
alias MediaServer.Repo
8+
9+
alias MediaServer.WatchStatuses.Movie
10+
11+
@doc """
12+
Returns the list of movie_watch_statuses.
13+
14+
## Examples
15+
16+
iex> list_movie_watch_statuses()
17+
[%Movie{}, ...]
18+
19+
"""
20+
def list_movie_watch_statuses do
21+
Repo.all(Movie)
22+
end
23+
24+
@doc """
25+
Gets a single movie.
26+
27+
Raises `Ecto.NoResultsError` if the Movie does not exist.
28+
29+
## Examples
30+
31+
iex> get_movie!(123)
32+
%Movie{}
33+
34+
iex> get_movie!(456)
35+
** (Ecto.NoResultsError)
36+
37+
"""
38+
def get_movie!(id), do: Repo.get!(Movie, id)
39+
40+
@doc """
41+
Creates a movie.
42+
43+
## Examples
44+
45+
iex> create_movie(%{field: value})
46+
{:ok, %Movie{}}
47+
48+
iex> create_movie(%{field: bad_value})
49+
{:error, %Ecto.Changeset{}}
50+
51+
"""
52+
def create_movie(attrs \\ %{}) do
53+
%Movie{}
54+
|> Movie.changeset(attrs)
55+
|> Repo.insert()
56+
end
57+
58+
@doc """
59+
Updates a movie.
60+
61+
## Examples
62+
63+
iex> update_movie(movie, %{field: new_value})
64+
{:ok, %Movie{}}
65+
66+
iex> update_movie(movie, %{field: bad_value})
67+
{:error, %Ecto.Changeset{}}
68+
69+
"""
70+
def update_movie(%Movie{} = movie, attrs) do
71+
movie
72+
|> Movie.changeset(attrs)
73+
|> Repo.update()
74+
end
75+
76+
def update_or_create_movie(attrs) do
77+
movie = Repo.get_by(Movie, [movie_id: attrs.movie_id, user_id: attrs.user_id])
78+
79+
case movie do
80+
nil ->
81+
create_movie(attrs)
82+
83+
_ ->
84+
update_movie(movie, attrs)
85+
end
86+
end
87+
88+
@doc """
89+
Deletes a movie.
90+
91+
## Examples
92+
93+
iex> delete_movie(movie)
94+
{:ok, %Movie{}}
95+
96+
iex> delete_movie(movie)
97+
{:error, %Ecto.Changeset{}}
98+
99+
"""
100+
def delete_movie(%Movie{} = movie) do
101+
Repo.delete(movie)
102+
end
103+
104+
@doc """
105+
Returns an `%Ecto.Changeset{}` for tracking movie changes.
106+
107+
## Examples
108+
109+
iex> change_movie(movie)
110+
%Ecto.Changeset{data: %Movie{}}
111+
112+
"""
113+
def change_movie(%Movie{} = movie, attrs \\ %{}) do
114+
Movie.changeset(movie, attrs)
115+
end
116+
117+
alias MediaServer.WatchStatuses.Episode
118+
119+
@doc """
120+
Returns the list of episode_watch_statuses.
121+
122+
## Examples
123+
124+
iex> list_episode_watch_statuses()
125+
[%Episode{}, ...]
126+
127+
"""
128+
def list_episode_watch_statuses do
129+
Repo.all(Episode)
130+
end
131+
132+
@doc """
133+
Gets a single episode.
134+
135+
Raises `Ecto.NoResultsError` if the Episode does not exist.
136+
137+
## Examples
138+
139+
iex> get_episode!(123)
140+
%Episode{}
141+
142+
iex> get_episode!(456)
143+
** (Ecto.NoResultsError)
144+
145+
"""
146+
def get_episode!(id), do: Repo.get!(Episode, id)
147+
148+
@doc """
149+
Creates a episode.
150+
151+
## Examples
152+
153+
iex> create_episode(%{field: value})
154+
{:ok, %Episode{}}
155+
156+
iex> create_episode(%{field: bad_value})
157+
{:error, %Ecto.Changeset{}}
158+
159+
"""
160+
def create_episode(attrs \\ %{}) do
161+
%Episode{}
162+
|> Episode.changeset(attrs)
163+
|> Repo.insert()
164+
end
165+
166+
@doc """
167+
Updates a episode.
168+
169+
## Examples
170+
171+
iex> update_episode(episode, %{field: new_value})
172+
{:ok, %Episode{}}
173+
174+
iex> update_episode(episode, %{field: bad_value})
175+
{:error, %Ecto.Changeset{}}
176+
177+
"""
178+
def update_episode(%Episode{} = episode, attrs) do
179+
episode
180+
|> Episode.changeset(attrs)
181+
|> Repo.update()
182+
end
183+
184+
def update_or_create_episode(attrs) do
185+
episode = Repo.get_by(Episode, [episode_id: attrs.episode_id, serie_id: attrs.serie_id, user_id: attrs.user_id])
186+
187+
case episode do
188+
nil ->
189+
create_episode(attrs)
190+
191+
_ ->
192+
update_episode(episode, attrs)
193+
end
194+
end
195+
196+
@doc """
197+
Deletes a episode.
198+
199+
## Examples
200+
201+
iex> delete_episode(episode)
202+
{:ok, %Episode{}}
203+
204+
iex> delete_episode(episode)
205+
{:error, %Ecto.Changeset{}}
206+
207+
"""
208+
def delete_episode(%Episode{} = episode) do
209+
Repo.delete(episode)
210+
end
211+
212+
@doc """
213+
Returns an `%Ecto.Changeset{}` for tracking episode changes.
214+
215+
## Examples
216+
217+
iex> change_episode(episode)
218+
%Ecto.Changeset{data: %Episode{}}
219+
220+
"""
221+
def change_episode(%Episode{} = episode, attrs \\ %{}) do
222+
Episode.changeset(episode, attrs)
223+
end
224+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
defmodule MediaServer.WatchStatuses.Episode do
2+
use Ecto.Schema
3+
import Ecto.Changeset
4+
5+
schema "episode_watch_statuses" do
6+
field :current_time, :integer
7+
field :duration, :integer
8+
field :episode_id, :integer
9+
field :image_url, :string
10+
field :serie_id, :integer
11+
field :title, :string
12+
belongs_to :user, MediaServer.Accounts.User
13+
14+
timestamps()
15+
end
16+
17+
@doc false
18+
def changeset(episode, attrs) do
19+
episode
20+
|> cast(attrs, [:episode_id, :serie_id, :title, :image_url, :current_time, :duration, :user_id])
21+
|> validate_required([:episode_id, :serie_id, :title, :current_time, :duration, :user_id])
22+
end
23+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
defmodule MediaServer.WatchStatuses.Movie do
2+
use Ecto.Schema
3+
import Ecto.Changeset
4+
5+
schema "movie_watch_statuses" do
6+
field :movie_id, :integer
7+
field :title, :string
8+
field :image_url, :string
9+
field :current_time, :integer
10+
field :duration, :integer
11+
belongs_to :user, MediaServer.Accounts.User
12+
13+
timestamps()
14+
end
15+
16+
@doc false
17+
def changeset(movie, attrs) do
18+
movie
19+
|> cast(attrs, [:movie_id, :title, :image_url, :current_time, :duration, :user_id])
20+
|> validate_required([:movie_id, :title, :current_time, :duration, :user_id])
21+
end
22+
end

lib/media_server_web.ex

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ defmodule MediaServerWeb do
8383

8484
# Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc)
8585
import Phoenix.LiveView.Helpers
86-
import MediaServerWeb.LiveHelpers
8786

8887
# Import basic rendering functionality (render, render_layout, etc)
8988
import Phoenix.View
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule MediaServerWeb.Components.StatusComponent do
2+
use MediaServerWeb, :live_component
3+
4+
alias MediaServer.WatchStatuses
5+
6+
def handle_event("delete", %{"id" => id}, socket) do
7+
WatchStatuses.delete_movie(WatchStatuses.get_movie!(id))
8+
9+
{
10+
:noreply,
11+
socket
12+
|> push_redirect(to: socket.assigns.return_to)
13+
}
14+
end
15+
16+
def handle_event("delete_episode", %{"id" => id}, socket) do
17+
WatchStatuses.delete_episode(WatchStatuses.get_episode!(id))
18+
19+
{
20+
:noreply,
21+
socket
22+
|> push_redirect(to: socket.assigns.return_to)
23+
}
24+
end
25+
end

0 commit comments

Comments
 (0)