rubypitaya 3.15.2 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/rubypitaya/app-template/Gemfile +1 -1
- data/lib/rubypitaya/app-template/Gemfile.lock +2 -2
- data/lib/rubypitaya/app-template/Makefile +4 -4
- data/lib/rubypitaya/app-template/app/setup/rubypitaya.yml +1 -1
- data/lib/rubypitaya/app-template/docker/dev/Dockerfile +1 -1
- data/lib/rubypitaya/app-template/docker/prod/Dockerfile +6 -3
- data/lib/rubypitaya/app-template/docker/stag/docker-compose.stag.yaml +67 -0
- data/lib/rubypitaya/app-template/docker/stag/update_image +6 -0
- data/lib/rubypitaya/app-template/docker-compose.yml +9 -4
- data/lib/rubypitaya/core/config_core.rb +1 -1
- data/lib/rubypitaya/core/etcd_connector.rb +16 -6
- data/lib/rubypitaya/core/main.rb +58 -57
- data/lib/rubypitaya/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 233f98eb7f7752ede2d0ce8caea9cba724b287f2fb537cab13b24f30bdad2be8
|
|
4
|
+
data.tar.gz: 685e591d3cf68cf0e094bcea484e7afed01c45e11cadd3f9639f7d00689bcbf9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92052de17be30fd1cd49ab3890caf7490a8cda3dafa7d2b9333efc3101d50532401e17700a98b637202478621170e0874f7c8e47e8b39f852417b30d87214a65
|
|
7
|
+
data.tar.gz: 8f27d2bce28666eeae9da9b0e08fdec7e8b85f175293be92dcffccb1af32fe45a7a60ecd089f6f8e3819111af7bfe31538d709c07791e69e82d1a64527f64f54
|
|
@@ -94,7 +94,7 @@ GEM
|
|
|
94
94
|
rspec-support (~> 3.12.0)
|
|
95
95
|
rspec-support (3.12.0)
|
|
96
96
|
ruby2_keywords (0.0.5)
|
|
97
|
-
rubypitaya (3.
|
|
97
|
+
rubypitaya (3.16.0)
|
|
98
98
|
activerecord (= 7.0.4)
|
|
99
99
|
etcdv3 (= 0.11.5)
|
|
100
100
|
google-protobuf (= 3.21.12)
|
|
@@ -132,7 +132,7 @@ DEPENDENCIES
|
|
|
132
132
|
listen (= 3.8.0)
|
|
133
133
|
pry (= 0.14.2)
|
|
134
134
|
rspec (= 3.12.0)
|
|
135
|
-
rubypitaya (= 3.
|
|
135
|
+
rubypitaya (= 3.16.0)
|
|
136
136
|
sinatra-contrib (= 3.0.5)
|
|
137
137
|
|
|
138
138
|
BUNDLED WITH
|
|
@@ -14,10 +14,10 @@ run:
|
|
|
14
14
|
build:
|
|
15
15
|
@docker-compose build
|
|
16
16
|
@docker-compose pull
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
# @echo "Copying vendor folder ..."
|
|
18
|
+
# @rm -rf ./vendor
|
|
19
|
+
# @sh -c "FOLDER=$(notdir $(shell pwd)); docker-compose run --no-deps --detach --name=rubypitaya rubypitaya; docker cp rubypitaya:/app/rubypitaya/vendor ."
|
|
20
|
+
# @docker rm -f rubypitaya
|
|
21
21
|
|
|
22
22
|
## Setup project dependencies
|
|
23
23
|
setup: clear
|
|
@@ -18,7 +18,7 @@ COPY Gemfile Gemfile.lock ./
|
|
|
18
18
|
RUN bundle config --global jobs 4 && \
|
|
19
19
|
bundle config --global set clean 'true' \
|
|
20
20
|
bundle config --global git.allow_insecure true && \
|
|
21
|
-
bundle config set path './vendor/bundle' && \
|
|
21
|
+
# bundle config set path './vendor/bundle' && \
|
|
22
22
|
bundle install
|
|
23
23
|
|
|
24
24
|
COPY . .
|
|
@@ -9,19 +9,20 @@ RUN apt update && \
|
|
|
9
9
|
|
|
10
10
|
COPY Gemfile Gemfile.lock ./
|
|
11
11
|
|
|
12
|
-
RUN bundle config --global
|
|
12
|
+
RUN bundle config set --global without "development" \
|
|
13
|
+
bundle config --global jobs 4 && \
|
|
13
14
|
bundle config --global set clean 'true' \
|
|
14
15
|
bundle config --global git.allow_insecure true && \
|
|
15
|
-
bundle config --global without "development test" && \
|
|
16
16
|
bundle install
|
|
17
17
|
|
|
18
18
|
RUN rm -rf /usr/local/bundle/cache && \
|
|
19
19
|
rm -rf /usr/local/bundle/build_info && \
|
|
20
20
|
rm -rf /usr/local/bundle/doc && \
|
|
21
21
|
rm -rf /usr/local/bundle/doc && \
|
|
22
|
-
find /usr/local/bundle/gems/grpc-*/src/ruby/lib/grpc/2.5/ -name "*.so" -delete && \
|
|
23
22
|
find /usr/local/bundle/gems/grpc-*/src/ruby/lib/grpc/2.6/ -name "*.so" -delete && \
|
|
24
23
|
find /usr/local/bundle/gems/grpc-*/src/ruby/lib/grpc/2.7/ -name "*.so" -delete && \
|
|
24
|
+
find /usr/local/bundle/gems/grpc-*/src/ruby/lib/grpc/3.0/ -name "*.so" -delete && \
|
|
25
|
+
find /usr/local/bundle/gems/grpc-*/src/ruby/lib/grpc/3.2/ -name "*.so" -delete && \
|
|
25
26
|
find /usr/local/bundle/gems/ -name "*.c" -delete && \
|
|
26
27
|
find /usr/local/bundle/gems/ -name "*.o" -delete && \
|
|
27
28
|
find /usr/local/bundle/gems/ -name "spec" -exec rm -rv {} +
|
|
@@ -46,6 +47,8 @@ COPY --from=builder /usr/local/bundle /usr/local/bundle
|
|
|
46
47
|
|
|
47
48
|
WORKDIR /app/rubypitaya/
|
|
48
49
|
|
|
50
|
+
RUN bundle config set --global without "development"
|
|
51
|
+
|
|
49
52
|
COPY . .
|
|
50
53
|
|
|
51
54
|
ENTRYPOINT ["./docker/entrypoint.sh"]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
version: '3'
|
|
2
|
+
services:
|
|
3
|
+
nats:
|
|
4
|
+
image: 'nats:2.6.2-alpine'
|
|
5
|
+
restart: always
|
|
6
|
+
ports:
|
|
7
|
+
- '4222:4222'
|
|
8
|
+
|
|
9
|
+
etcd:
|
|
10
|
+
image: 'quay.io/coreos/etcd:v3.5.5'
|
|
11
|
+
restart: always
|
|
12
|
+
environment:
|
|
13
|
+
ETCD_ADVERTISE_CLIENT_URLS: 'http://localhost:2379'
|
|
14
|
+
ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
15
|
+
ports:
|
|
16
|
+
- '2379:2379'
|
|
17
|
+
|
|
18
|
+
redis:
|
|
19
|
+
image: 'redis:6.0.5-alpine'
|
|
20
|
+
restart: always
|
|
21
|
+
ports:
|
|
22
|
+
- '9001:6379'
|
|
23
|
+
|
|
24
|
+
db:
|
|
25
|
+
image: 'postgres:14.0-alpine'
|
|
26
|
+
restart: always
|
|
27
|
+
volumes:
|
|
28
|
+
- 'postgres:/var/lib/postgresql/data'
|
|
29
|
+
environment:
|
|
30
|
+
POSTGRES_USER: 'postgres'
|
|
31
|
+
POSTGRES_PASSWORD: 'postgres'
|
|
32
|
+
ports:
|
|
33
|
+
- '9100:5432'
|
|
34
|
+
|
|
35
|
+
connector:
|
|
36
|
+
image: 'registry.gitlab.com/lucianopc/pitaya-connector:0.1.0'
|
|
37
|
+
restart: always
|
|
38
|
+
depends_on:
|
|
39
|
+
- 'db'
|
|
40
|
+
- 'nats'
|
|
41
|
+
- 'etcd'
|
|
42
|
+
- 'redis'
|
|
43
|
+
ports:
|
|
44
|
+
- '3250:3250'
|
|
45
|
+
environment:
|
|
46
|
+
PITAYA_CLUSTER_RPC_SERVER_NATS_CONNECT: 'nats://nats:4222'
|
|
47
|
+
PITAYA_CLUSTER_RPC_CLIENT_NATS_CONNECT: 'nats://nats:4222'
|
|
48
|
+
PITAYA_CLUSTER_RPC_CLIENT_NATS_REQUESTTIMEOUT: '10s'
|
|
49
|
+
PITAYA_CLUSTER_SD_ETCD_ENDPOINTS: 'etcd:2379'
|
|
50
|
+
PITAYA_CLUSTER_SD_ETCD_PREFIX: 'rubypitaya/'
|
|
51
|
+
SERVER_ROUTES: 'rubypitaya'
|
|
52
|
+
|
|
53
|
+
rubypitaya:
|
|
54
|
+
image: 'registry.gitlab.com/dungeonrpg/dungeonrpg/backend:${RUBYPITAYA_IMAGE_TAG}'
|
|
55
|
+
restart: always
|
|
56
|
+
depends_on:
|
|
57
|
+
- 'db'
|
|
58
|
+
- 'nats'
|
|
59
|
+
- 'etcd'
|
|
60
|
+
- 'redis'
|
|
61
|
+
- 'connector'
|
|
62
|
+
working_dir: '/app/rubypitaya'
|
|
63
|
+
environment:
|
|
64
|
+
RUBYPITAYA_SERVER_ENVIRONMENT: 'production'
|
|
65
|
+
|
|
66
|
+
volumes:
|
|
67
|
+
postgres:
|
|
@@ -6,10 +6,12 @@ services:
|
|
|
6
6
|
- '4222:4222'
|
|
7
7
|
|
|
8
8
|
etcd:
|
|
9
|
-
image: '
|
|
9
|
+
image: 'quay.io/coreos/etcd:v3.5.5'
|
|
10
|
+
environment:
|
|
11
|
+
ETCD_ADVERTISE_CLIENT_URLS: 'http://localhost:2379'
|
|
12
|
+
ETCD_LISTEN_CLIENT_URLS: 'http://0.0.0.0:2379'
|
|
10
13
|
ports:
|
|
11
14
|
- '2379:2379'
|
|
12
|
-
- '2380:2380'
|
|
13
15
|
|
|
14
16
|
redis:
|
|
15
17
|
image: 'redis:6.0.5-alpine'
|
|
@@ -56,7 +58,8 @@ services:
|
|
|
56
58
|
working_dir: '/app/rubypitaya'
|
|
57
59
|
volumes:
|
|
58
60
|
- .:/app/rubypitaya
|
|
59
|
-
- /app/rubypitaya/vendor
|
|
61
|
+
# - /app/rubypitaya/vendor
|
|
62
|
+
# - ./vendor/bundle/ruby/3.1.0/gems/rubypitaya-3.13.0/lib/rubypitaya/core:/app/rubypitaya/vendor/bundle/ruby/3.1.0/gems/rubypitaya-3.13.0/lib/rubypitaya/core
|
|
60
63
|
ports:
|
|
61
64
|
- '80:4567'
|
|
62
65
|
environment:
|
|
@@ -65,12 +68,14 @@ services:
|
|
|
65
68
|
rubypitaya-console:
|
|
66
69
|
<<: *rubypitaya
|
|
67
70
|
ports: []
|
|
68
|
-
|
|
71
|
+
command: 'exit'
|
|
72
|
+
|
|
69
73
|
rubypitaya-commands:
|
|
70
74
|
<<: *rubypitaya
|
|
71
75
|
depends_on:
|
|
72
76
|
- 'db'
|
|
73
77
|
ports: []
|
|
78
|
+
command: 'exit'
|
|
74
79
|
|
|
75
80
|
volumes:
|
|
76
81
|
postgres:
|
|
@@ -46,7 +46,7 @@ module RubyPitaya
|
|
|
46
46
|
def load_config_file(configs_folder_path, file_path)
|
|
47
47
|
config_text = File.open(file_path, &:read)
|
|
48
48
|
config_hash = JSON.parse(config_text, symbolize_names: true)
|
|
49
|
-
config_hash.extend(ConfigHashExtension)
|
|
49
|
+
config_hash.extend(ConfigHashExtension) if config_hash.is_a?(Hash)
|
|
50
50
|
|
|
51
51
|
file_name = file_path.sub(/^#{configs_folder_path}/, '')[0..-6]
|
|
52
52
|
|
|
@@ -6,7 +6,7 @@ module RubyPitaya
|
|
|
6
6
|
class EtcdConnector
|
|
7
7
|
|
|
8
8
|
def initialize(server_uuid, desktop_name, server_name, etcd_prefix,
|
|
9
|
-
etcd_address, allow_reconnect, lease_seconds)
|
|
9
|
+
etcd_address, allow_reconnect, lease_seconds, log)
|
|
10
10
|
@server_uuid = server_uuid
|
|
11
11
|
@server_name = server_name
|
|
12
12
|
@desktop_name = desktop_name
|
|
@@ -14,6 +14,7 @@ module RubyPitaya
|
|
|
14
14
|
@etcd_address = etcd_address
|
|
15
15
|
@allow_reconnect = allow_reconnect
|
|
16
16
|
@lease_seconds = lease_seconds
|
|
17
|
+
@log = log
|
|
17
18
|
|
|
18
19
|
@renew_connection_seconds = lease_seconds / 2.0
|
|
19
20
|
|
|
@@ -27,7 +28,7 @@ module RubyPitaya
|
|
|
27
28
|
connection_value = get_connection_value
|
|
28
29
|
|
|
29
30
|
@connection = Etcdv3.new(endpoints: @etcd_address,
|
|
30
|
-
|
|
31
|
+
allow_reconnect: @allow_reconnect)
|
|
31
32
|
|
|
32
33
|
@lease = @connection.lease_grant(@lease_seconds)
|
|
33
34
|
|
|
@@ -40,6 +41,7 @@ module RubyPitaya
|
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
def disconnect
|
|
44
|
+
stop_renew_connection
|
|
43
45
|
@connection.del(get_connection_key)
|
|
44
46
|
end
|
|
45
47
|
|
|
@@ -60,15 +62,23 @@ module RubyPitaya
|
|
|
60
62
|
loop do
|
|
61
63
|
sleep(@renew_connection_seconds)
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
begin
|
|
66
|
+
@lease = @connection.lease_keep_alive_once(@lease.ID)
|
|
67
|
+
|
|
68
|
+
if @lease.TTL == 0
|
|
69
|
+
@lease = @connection.lease_grant(@lease_seconds)
|
|
70
|
+
@connection.put(@renew_connection_key, @renew_connection_value, lease: @lease.ID)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
rescue Exception => error
|
|
74
|
+
@log.error "ETCD RENEW ERROR: #{error.class} | #{error.message}} \n #{error.backtrace.join("\n")}"
|
|
75
|
+
end
|
|
66
76
|
end
|
|
67
77
|
end
|
|
68
78
|
end
|
|
69
79
|
|
|
70
80
|
def stop_renew_connection
|
|
71
|
-
@renew_connection_thread.exit unless @renew_connection_thread.nil?
|
|
81
|
+
@renew_connection_thread.exit.join unless @renew_connection_thread.nil?
|
|
72
82
|
end
|
|
73
83
|
|
|
74
84
|
def get_server_data
|
data/lib/rubypitaya/core/main.rb
CHANGED
|
@@ -50,12 +50,12 @@ module RubyPitaya
|
|
|
50
50
|
|
|
51
51
|
@etcd_prefix = @setup['rubypitaya.etcd.prefix']
|
|
52
52
|
@etcd_address = @setup['rubypitaya.etcd.url']
|
|
53
|
-
@etcd_lease_seconds = @setup['rubypitaya.etcd.leaseSeconds']
|
|
53
|
+
@etcd_lease_seconds = @setup['rubypitaya.etcd.leaseSeconds']
|
|
54
54
|
@allow_reconnect = false
|
|
55
55
|
@etcd_connector = EtcdConnector.new(@service_uuid, @desktop_name,
|
|
56
56
|
@server_name, @etcd_prefix,
|
|
57
57
|
@etcd_address, @allow_reconnect,
|
|
58
|
-
@etcd_lease_seconds)
|
|
58
|
+
@etcd_lease_seconds, @log)
|
|
59
59
|
@etcd_connector.connect
|
|
60
60
|
|
|
61
61
|
@nats_address = @setup['rubypitaya.nats.url']
|
|
@@ -148,63 +148,64 @@ module RubyPitaya
|
|
|
148
148
|
end
|
|
149
149
|
|
|
150
150
|
def run_nats_connection
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
151
|
+
loop do
|
|
152
|
+
@nats_connector.connect do |request|
|
|
153
|
+
start_time_seconds = Time.now.to_f
|
|
154
|
+
|
|
155
|
+
message_route = request[:msg][:route]
|
|
156
|
+
message_data = request[:msg][:data]
|
|
157
|
+
message_json = JSON.parse(message_data) if !message_data.nil? && !message_data.empty?
|
|
158
|
+
|
|
159
|
+
params = Parameters.new(message_json || {})
|
|
160
|
+
|
|
161
|
+
session_id = request[:session][:id]
|
|
162
|
+
session_uid = request[:session].fetch(:uid, '')
|
|
163
|
+
session_data = request[:session][:data]
|
|
164
|
+
session_data = JSON.parse(session_data, symbolize_names: true)
|
|
165
|
+
frontend_id = request[:frontendID]
|
|
166
|
+
metadata = request[:metadata]
|
|
167
|
+
metadata = JSON.parse(metadata, symbolize_names: true)
|
|
168
|
+
|
|
169
|
+
@session.update(session_id, session_uid, session_data, metadata,
|
|
170
|
+
frontend_id)
|
|
171
|
+
|
|
172
|
+
handler_name, action_name = message_route.scan(/\A\w+\.(\w+)\.(\w+)\z/)[0]
|
|
173
|
+
|
|
174
|
+
@log.info "request -> route: #{message_route}"
|
|
175
|
+
@log.info " -> data: #{message_data}"
|
|
176
|
+
|
|
177
|
+
@config.clear_cache
|
|
178
|
+
|
|
179
|
+
response = {}
|
|
180
|
+
|
|
181
|
+
begin
|
|
182
|
+
response = @handler_router.call(handler_name, action_name, @session,
|
|
183
|
+
@postman, @services, @setup, @config,
|
|
184
|
+
@log, params)
|
|
185
|
+
rescue RouteError => error
|
|
186
|
+
@log.error "ROUTE ERROR: #{error.code} | #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}"
|
|
187
|
+
response = {
|
|
188
|
+
code: error.code,
|
|
189
|
+
message: error.message
|
|
190
|
+
}
|
|
191
|
+
rescue Exception => error
|
|
192
|
+
@log.error "INTERNAL ERROR: #{error.class} | #{error.message} \n #{error.backtrace.join("\n")}"
|
|
193
|
+
response = {
|
|
194
|
+
code: StatusCodes::CODE_INTERNAL_ERROR,
|
|
195
|
+
message: StatusCodes::MESSAGE_INTERNAL_ERROR,
|
|
196
|
+
}
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
response_json = JSON.generate(response)
|
|
200
|
+
delta_time_seconds = ((Time.now.to_f - start_time_seconds) * 1000).round(2)
|
|
201
|
+
|
|
202
|
+
@log.info "response [#{delta_time_seconds}ms] -> #{response_json}"
|
|
203
|
+
|
|
204
|
+
response_json
|
|
196
205
|
end
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
delta_time_seconds = ((Time.now.to_f - start_time_seconds) * 1000).round(2)
|
|
200
|
-
|
|
201
|
-
@log.info "response [#{delta_time_seconds}ms] -> #{response_json}"
|
|
202
|
-
|
|
203
|
-
response_json
|
|
206
|
+
rescue Exception => error
|
|
207
|
+
@log.error "INTERNAL ERROR: #{error.class} | #{error.message}} \n #{error.backtrace.join("\n")}"
|
|
204
208
|
end
|
|
205
|
-
rescue Exception => error
|
|
206
|
-
@log.error "INTERNAL ERROR: #{error.class} | #{error.message}} \n #{error.backtrace.join("\n")}"
|
|
207
|
-
run_nats_connection
|
|
208
209
|
end
|
|
209
210
|
end
|
|
210
211
|
end
|
data/lib/rubypitaya/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubypitaya
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Luciano Prestes Cavalcanti
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-12-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pg
|
|
@@ -269,6 +269,8 @@ files:
|
|
|
269
269
|
- "./lib/rubypitaya/app-template/docker/dev/Dockerfile"
|
|
270
270
|
- "./lib/rubypitaya/app-template/docker/entrypoint.sh"
|
|
271
271
|
- "./lib/rubypitaya/app-template/docker/prod/Dockerfile"
|
|
272
|
+
- "./lib/rubypitaya/app-template/docker/stag/docker-compose.stag.yaml"
|
|
273
|
+
- "./lib/rubypitaya/app-template/docker/stag/update_image"
|
|
272
274
|
- "./lib/rubypitaya/app-template/docker/wait_for.sh"
|
|
273
275
|
- "./lib/rubypitaya/app-template/features/hello_world.feature"
|
|
274
276
|
- "./lib/rubypitaya/app-template/features/player.feature"
|